diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..db4c6d9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +dist +node_modules \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..472c674 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +JWT_SECRET=secret + +DATABASE_URL="postgresql://user:password@host:port/database?schema=public" + +PORT=3001 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/.gitignore b/.gitignore index 68c9d3d..6151ec2 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,5 @@ dist package-lock.json -prisma/migrations \ No newline at end of file +prisma/migrations +.env.prod diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ee001cd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:18.0.0 + +WORKDIR /usr/src/api + +COPY . . + +RUN npm install --quiet --no-optional --no-fund --loglevel=error + +RUN npm run build + +EXPOSE 3000 + +CMD [ "npm", "run", "start:prod" ] + +RUN npx prisma reset \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9e560b7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: '3.9' + +services: + postgres: + image: postgres:latest + container_name: controlserra + restart: always + environment: + POSTGRES_USER: controlserra + POSTGRES_PASSWORD: controlserra + POSTGRES_DB: controlserra + ports: + - "5436:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + pgadmin: + image: dpage/pgadmin4:latest + container_name: controlserra_pgadmin + restart: always + environment: + PGADMIN_DEFAULT_EMAIL: controlserra + PGADMIN_DEFAULT_PASSWORD: controlserra + ports: + - "5056:80" + depends_on: + - postgres + + +volumes: + postgres_data: diff --git a/package-lock.json b/package-lock.json index a5648f6..0023d41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,25 +10,37 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", + "@nestjs/passport": "^10.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.14", - "@prisma/client": "^5.4.2", + "@prisma/client": "^5.5.2", + "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "control-serra-backend": "file:", "express": "^4.18.2", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "swagger-ui-express": "^5.0.0" }, "devDependencies": { + "@faker-js/faker": "^8.2.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", + "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-jwt": "^3.0.13", + "@types/passport-local": "^1.0.38", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", @@ -941,6 +953,22 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz", + "integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -1426,6 +1454,61 @@ "node": ">=8" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@nestjs/cli": { "version": "10.1.18", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.18.tgz", @@ -1571,6 +1654,21 @@ } } }, + "node_modules/@nestjs/config": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz", + "integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.5", + "dotenv-expand": "10.0.0", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "rxjs": "^7.1.0" + } + }, "node_modules/@nestjs/core": { "version": "10.2.7", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.7.tgz", @@ -1608,6 +1706,18 @@ } } }, + "node_modules/@nestjs/jwt": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", + "integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==", + "dependencies": { + "@types/jsonwebtoken": "9.0.5", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/@nestjs/mapped-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", @@ -1627,6 +1737,15 @@ } } }, + "node_modules/@nestjs/passport": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-10.0.2.tgz", + "integrity": "sha512-od31vfB2z3y05IDB5dWSbCGE2+pAf2k2WCBinNuTTOxN0O0+wtO1L3kawj/aCW3YR9uxsTOVbTDwtwgpNNsnjQ==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "passport": "^0.4.0 || ^0.5.0 || ^0.6.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "10.2.7", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.7.tgz", @@ -1850,12 +1969,12 @@ } }, "node_modules/@prisma/client": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.2.tgz", - "integrity": "sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.5.2.tgz", + "integrity": "sha512-54XkqR8M+fxbzYqe+bIXimYnkkcGqgOh0dn0yWtIk6CQT4IUCAvNFNcQZwk2KqaLU+/1PHTSWrcHtx4XjluR5w==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" + "@prisma/engines-version": "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a" }, "engines": { "node": ">=16.13" @@ -1877,9 +1996,9 @@ "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz", - "integrity": "sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA==" + "version": "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a.tgz", + "integrity": "sha512-O+qHFnZvAyOFk1tUco2/VdiqS0ym42a3+6CYLScllmnpbyiTplgyLt2rK/B9BTjYkSHjrgMhkG47S0oqzdIckA==" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -1970,6 +2089,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.4", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", @@ -2100,6 +2228,14 @@ "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", @@ -2110,7 +2246,6 @@ "version": "20.8.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", - "dev": true, "dependencies": { "undici-types": "~5.25.1" } @@ -2121,6 +2256,47 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/passport": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.15.tgz", + "integrity": "sha512-oHOgzPBp5eLI1U/7421qYV/ZySQXMYCBSfRkDe1tQ0YrIbLY/M/76qIXE7Bs7lFyvw1x5QqiNQ9imvh0fQHe9Q==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.13.tgz", + "integrity": "sha512-fjHaC6Bv8EpMMqzTnHP32SXlZGaNfBPC/Po5dmRGYi2Ky7ljXPbGnOy+SxZqa6iZvFgVhoJ1915Re3m93zmcfA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", + "integrity": "sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.8", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", @@ -2552,6 +2728,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2603,6 +2784,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -2676,7 +2868,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2713,6 +2904,36 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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", @@ -2875,8 +3096,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2898,6 +3118,19 @@ } ] }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -2993,7 +3226,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3088,6 +3320,11 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3234,6 +3471,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -3399,6 +3644,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3445,8 +3698,7 @@ "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 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -3467,6 +3719,11 @@ "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3486,6 +3743,10 @@ "node": ">= 0.6" } }, + "node_modules/control-serra-backend": { + "resolved": "", + "link": true + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3589,7 +3850,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -3808,6 +4068,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3825,6 +4090,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3886,6 +4159,35 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3912,8 +4214,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -4669,6 +4970,33 @@ "node": ">=12" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/fs-monkey": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", @@ -4678,8 +5006,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -4703,6 +5030,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4760,7 +5106,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4888,6 +5233,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -4918,6 +5268,18 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5015,7 +5377,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5127,7 +5488,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, "engines": { "node": ">=8" } @@ -6000,6 +6360,46 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6080,6 +6480,36 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6092,6 +6522,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -6275,7 +6710,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6300,6 +6734,34 @@ "node": ">=8" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6314,8 +6776,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { "version": "1.4.4-lts.1", @@ -6366,6 +6827,11 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -6406,6 +6872,20 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6427,6 +6907,17 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6458,7 +6949,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -6638,6 +7128,51 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6651,7 +7186,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6719,6 +7253,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7379,7 +7918,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -7394,7 +7932,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7405,8 +7942,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { "version": "0.18.0", @@ -7472,6 +8008,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -7531,8 +8072,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sisteransi": { "version": "1.0.5", @@ -7650,7 +8190,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7664,7 +8203,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7824,6 +8362,46 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/terser": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", @@ -8247,8 +8825,7 @@ "node_modules/undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", - "dev": true + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, "node_modules/universalify": { "version": "2.0.0", @@ -8514,6 +9091,14 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/windows-release": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", @@ -8593,8 +9178,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index a233b76..85198a4 100644 --- a/package.json +++ b/package.json @@ -11,35 +11,49 @@ "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", + "start:prod": "npm run reset-db && npm run seed && node dist/src/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "reset-db": "npx prisma migrate reset --force --skip-seed", + "seed": "ts-node prisma/seed.ts" }, "dependencies": { "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", + "@nestjs/passport": "^10.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.14", - "@prisma/client": "^5.4.2", + "@prisma/client": "^5.5.2", + "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "control-serra-backend": "file:", "express": "^4.18.2", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "swagger-ui-express": "^5.0.0" }, "devDependencies": { + "@faker-js/faker": "^8.2.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", + "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-jwt": "^3.0.13", + "@types/passport-local": "^1.0.38", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", @@ -76,5 +90,8 @@ }, "engines": { "node": "18" + }, + "prisma": { + "seed": "ts-node prisma/seed.ts" } -} +} \ No newline at end of file diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index e5a788a..fbffa92 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "mysql" \ No newline at end of file +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 28aaf1a..779336c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,7 +3,7 @@ generator client { } datasource db { - provider = "mysql" + provider = "postgresql" url = env("DATABASE_URL") } @@ -20,17 +20,13 @@ model Categoria { } model Insumo { - id Int @id @default(autoincrement()) - titulo String - descricao String? - unidadeMedida String? - idCategoria Int? - categoria Categoria? @relation(fields: [idCategoria], references: [id]) - cotacoes Cotacao[] - listaInsumo ListaInsumo[] - insumoProdutoBase InsumoProdutoBase[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + titulo String + idCategoria Int + categoria Categoria? @relation(fields: [idCategoria], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + variante Variante[] @@map("Insumos") } @@ -51,7 +47,7 @@ model Fornecedor { nome String? cpf String? @unique rg String? @unique - nomeFantasia String? @unique + nomeFantasia String? razaoSocial String? @unique cnpj String? @unique cotacaos Cotacao[] @@ -95,9 +91,11 @@ model Orcamento { totalMateriais Float? status status @default(Pendente) prazoEstimadoProducao Int? - observacoes String? @db.MediumText + observacoes String? idCliente Int idPedido Int? + criadorPor Int? + usuario Usuario? @relation(fields: [criadorPor], references: [id]) pedido Pedido? cliente Cliente @relation(fields: [idCliente], references: [id]) createdAt DateTime @default(now()) @@ -113,21 +111,25 @@ model Pedido { status status @default(Pendente) idOrcamento Int @unique orcamento Orcamento @relation(fields: [idOrcamento], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@map("Pedidos") } model Produto { - id Int @id @default(autoincrement()) - titulo String - quantidade Int? - valorUnitario Float? - observacoes String? - listaInsumos ListaInsumo[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - Orcamento Orcamento @relation(fields: [orcamentoId], references: [id]) - orcamentoId Int + id Int @id @default(autoincrement()) + titulo String + quantidade Float + valorUnitario Float? @default(0) + observacoes String? + valorMaterial Float @default(0) + valorMaoDeObra Float @default(0) + listaInsumos ListaInsumo[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Orcamento Orcamento @relation(fields: [idOrcamento], references: [id]) + idOrcamento Int @@map("Produtos") } @@ -135,12 +137,12 @@ model Produto { model Cotacao { id Int @id @default(autoincrement()) data DateTime - valor Float - unidade String + valor Float idFornecedor Int - idInsumo Int + idVariante Int + obsoleta Boolean? @default(false) fornecedor Fornecedor @relation(fields: [idFornecedor], references: [id]) - insumo Insumo @relation(fields: [idInsumo], references: [id]) + variante Variante @relation(fields: [idVariante], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt ListaInsumo ListaInsumo[] @@ -149,17 +151,18 @@ model Cotacao { } model ListaInsumo { - id Int @id @default(autoincrement()) - quantidade Float - idProduto Int - idInsumo Int - idCotacao Int? - unidade String? - produto Produto? @relation(fields: [idProduto], references: [id]) - insumo Insumo? @relation(fields: [idInsumo], references: [id]) - cotacao Cotacao? @relation(fields: [idCotacao], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + quantidade Float + idProduto Int + idVariante Int + idCotacao Int? + + valorUnitario Float? @default(0) + produto Produto? @relation(fields: [idProduto], references: [id]) + variante Variante? @relation(fields: [idVariante], references: [id]) + cotacao Cotacao? @relation(fields: [idCotacao], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@map("ListaInsumos") } @@ -172,13 +175,27 @@ model Usuario { email String @unique telefone String senha String - token String + orcamentos Orcamento[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("Usuarios") } +model Variante { + id Int @id @default(autoincrement()) + variante String + idInsumo Int + insumo Insumo @relation(fields: [idInsumo], references: [id]) + listaInsumo ListaInsumo[] + insumoProdutoBase InsumoProdutoBase[] + Cotacao Cotacao[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("Variantes") +} + model ProdutoBase { id Int @id @default(autoincrement()) titulo String @@ -191,14 +208,15 @@ model ProdutoBase { } model InsumoProdutoBase { - id Int @id @default(autoincrement()) - quantidade Float @default(1) + id Int @id @default(autoincrement()) + quantidade Float @default(1) idProdutoBase Int - idInsumo Int - produtoBase ProdutoBase? @relation(fields: [idProdutoBase], references: [id]) - insumos Insumo? @relation(fields: [idInsumo], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + idVariante Int + + produtoBase ProdutoBase @relation(fields: [idProdutoBase], references: [id]) + variantes Variante @relation(fields: [idVariante], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@map("InsumosProdutosBase") } @@ -207,15 +225,8 @@ enum contaTipo { Fisica Juridica } - -enum produtoTipo { - Base - Customizado -} - enum status { Pendente - Iniciado Em_Processo @map("Em Processo") Concluido } diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..31df3fc --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,1157 @@ +import { PrismaClient } from '@prisma/client'; +import { faker } from '@faker-js/faker'; +import { CreateUsuarioDto } from 'src/modules/usuarios/dto/create-usuario.dto'; +import * as bcrypt from 'bcrypt'; + +const prisma = new PrismaClient(); + +async function seedDatabase() { + const createUser = async (createUsuarioDto: CreateUsuarioDto) => { + if (true) { + createUsuarioDto.senha = await bcrypt.hash(createUsuarioDto.senha, 10); + const usuario = await prisma.usuario.create({ + data: createUsuarioDto, + }); + usuario.senha = undefined; + return usuario; + } + }; + //USUARIOS + await createUser({ + tipoUsuario: 'Administrador', + nome: 'Convidado', + cpf: '51960443967', + email: 'admin@admin.com', + telefone: '8138225561', + senha: 'admin', + }); + + await createUser({ + tipoUsuario: 'Serralheiro', + nome: 'Isis Letícia Monteiro', + cpf: '51960443968', + email: 'isis-monteiro75@eanac.com.br', + telefone: '67998947436', + senha: 'admin', + }); + + await createUser({ + tipoUsuario: 'Serralheiro', + nome: 'Fábio Marcos Araújo', + cpf: '65779714207', + email: 'fabio_araujo@transportadoratransdel.com.br', + telefone: '4426720740', + senha: 'admin', + }); + + await createUser({ + tipoUsuario: 'Serralheiro', + nome: 'Yuri Isaac Roberto Nascimento', + cpf: '01462971210', + email: 'yuriisaacnascimento@academiahct.com.br', + telefone: '96996038541', + senha: 'admin', + }); + + await createUser({ + tipoUsuario: 'Serralheiro', + nome: 'Oliver Matheus Aparício', + cpf: '31866807781', + email: 'oliver_matheus_aparicio@europamotors.com.br', + telefone: '85998182177', + senha: 'admin', + }); + + await createUser({ + tipoUsuario: 'Serralheiro', + nome: 'Luciana Lúcia Corte Real', + cpf: '05036225244', + email: 'luciana.lucia.cortereal@unipsicotaubate.com.br', + telefone: '84988383056', + senha: 'admin', + }); + + //CATEGORIAS + await prisma.categoria.create({ + data: { + tipo: 'Insumo', + titulo: 'Metais', + descricao: 'Ferros/Metais', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Mão de Obra', + titulo: 'Pinturas', + descricao: 'Serviço', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Insumo', + titulo: 'Serras', + descricao: '', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Insumo', + titulo: 'Laminas', + descricao: '', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Insumo', + titulo: 'Chapa', + descricao: '', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Insumo', + titulo: 'Tubos', + descricao: '', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Mão de Obra', + titulo: 'Portas', + descricao: 'Serviço', + }, + }); + await prisma.categoria.create({ + data: { + tipo: 'Mão de Obra', + titulo: 'Treliças', + descricao: 'Serviço', + }, + }); + + //INSUMOS + await prisma.insumo.create({ + data: { + titulo: 'Metalon', + idCategoria: 1, + }, + }); + + await prisma.insumo.create({ + data: { + titulo: 'Pintura', + idCategoria: 2, + }, + }); + await prisma.insumo.create({ + data: { + titulo: 'Disco de Corte', + idCategoria: 3, + }, + }); + await prisma.insumo.create({ + data: { + titulo: 'Placa Cimentícia', + idCategoria: 5, + }, + }); + await prisma.insumo.create({ + data: { + titulo: 'Barra Roscada', + idCategoria: 1, + }, + }); + await prisma.insumo.create({ + data: { + titulo: 'Chapa', + idCategoria: 4, + }, + }); + + //VARIANTES + await prisma.variante.create({ + data: { + variante: '50x30 Br 6m 3,00mm', + idInsumo: 1, + }, + }); + await prisma.variante.create({ + data: { + variante: '50x50 Br 8m 2,00mm', + idInsumo: 1, + }, + }); + await prisma.variante.create({ + data: { + variante: '50x30 Br 6m 2,00mm', + idInsumo: 1, + }, + }); + await prisma.variante.create({ + data: { + variante: '50x50 Br 6m 2,00mm', + idInsumo: 1, + }, + }); + await prisma.variante.create({ + data: { + variante: '50x50 Br 6m 1,50mm', + idInsumo: 1, + }, + }); + + await prisma.variante.create({ + data: { + variante: '4.1/2', + idInsumo: 3, + }, + }); + await prisma.variante.create({ + data: { + variante: '12 (Fino)', + idInsumo: 3, + }, + }); + await prisma.variante.create({ + data: { + variante: '7', + idInsumo: 3, + }, + }); + await prisma.variante.create({ + data: { + variante: '10mm 1,20 x 2,00', + idInsumo: 4, + }, + }); + await prisma.variante.create({ + data: { + variante: '10mm 1,20 x 2,40', + idInsumo: 4, + }, + }); + await prisma.variante.create({ + data: { + variante: '10mm 1,00 x 2,40', + idInsumo: 4, + }, + }); + + await prisma.variante.create({ + data: { + variante: '2,00 x 1,00 m', + idInsumo: 6, + }, + }); + await prisma.variante.create({ + data: { + variante: '4,00 x 2,40 m', + idInsumo: 6, + }, + }); + await prisma.variante.create({ + data: { + variante: '3,00 x 1,20 m', + idInsumo: 6, + }, + }); + await prisma.variante.create({ + data: { + variante: '2,00 x 1,20 m', + idInsumo: 6, + }, + }); + await prisma.variante.create({ + data: { + variante: 'Acrílico', + idInsumo: 2, + }, + }); + await prisma.variante.create({ + data: { + variante: 'esmalte', + idInsumo: 2, + }, + }); + await prisma.variante.create({ + data: { + variante: 'epóxi', + idInsumo: 2, + }, + }); + await prisma.variante.create({ + data: { + variante: 'látex', + idInsumo: 2, + }, + }); + //CLIENTES + await prisma.cliente.create({ + data: { + email: 'vitor_dacruz@viavaleseguros.com.br', + telefone: '98987098845', + contaTipo: 'Fisica', + nome: 'Vitor Renato Paulo da Cruz', + cpf: '94963377160', + rg: '235096167', + pais: 'Brasil', + cep: '65049290', + estado: 'MA', + cidade: 'São Luís', + bairro: 'Cutim Anil', + rua: 'Rua Antônio Vitória Cano', + numero: '21', + complemento: 'sobrado', + }, + }); + + await prisma.cliente.create({ + data: { + email: 'rafael_eduardo_darosa@orteca.com.br', + telefone: '95988444711', + contaTipo: 'Fisica', + nome: 'Rafael Eduardo José da Rosa', + cpf: '95457987183', + rg: '134988905', + pais: 'Brasil', + cep: '69317232', + estado: 'RR', + cidade: 'Boa Vista', + bairro: 'Alvorada', + rua: 'Rua Jorge Dias Carneiro', + numero: '17', + complemento: 'casa', + }, + }); + + await prisma.cliente.create({ + data: { + email: 'yago_vieira@bat.com', + telefone: '9636478278', + contaTipo: 'Fisica', + nome: 'Yago Bruno Vieira', + cpf: '59420571688', + rg: '262424630', + pais: 'Brasil', + cep: '68906084', + estado: 'AP', + cidade: 'Macapá', + bairro: 'Goiabal', + rua: 'Rua Angola', + numero: '86', + complemento: 'fundos', + }, + }); + + await prisma.cliente.create({ + data: { + email: 'auditoria@muriloestellatelecomunicacoesme.com.br', + telefone: '1128482451', + contaTipo: 'Juridica', + nomeFantasia: 'Murilo e Stella Telecomunicações ME', + cnpj: '58368409000145', + razaoSocial: 'Murilo e Stella Telecomunicações ME', + pais: 'Brasil', + cep: '12927040', + estado: 'SP', + cidade: 'Bragança Paulista', + bairro: 'Núcleo Residencial Henedina Rodrigues Cortez', + rua: 'Rua Gentil José de Matos', + numero: '704', + complemento: '', + }, + }); + + await prisma.cliente.create({ + data: { + email: 'administracao@joaoetaniagraficaltda.com.br', + telefone: '3538242833', + contaTipo: 'Juridica', + nomeFantasia: 'João e Tânia Gráfica Ltda', + cnpj: '79865260000180', + razaoSocial: 'João e Tânia Gráfica Ltda', + pais: 'Brasil', + cep: '37701064', + estado: 'MG', + cidade: 'Poços de Caldas', + bairro: 'Vila Nossa Senhora de Fátima', + rua: 'Alameda Edson', + numero: '275', + complemento: '', + }, + }); + + await prisma.cliente.create({ + data: { + email: 'presidencia@renaneliviacontabilltda.com.br', + telefone: '2138258163', + contaTipo: 'Juridica', + nomeFantasia: 'Renan e Lívia Contábil Ltda', + cnpj: '60861961000112', + razaoSocial: 'Renan e Lívia Contábil Ltda', + pais: 'Brasil', + cep: '22723590', + estado: 'RJ', + cidade: 'Rio de Janeiro', + bairro: 'Taquara', + rua: 'Rua dos Geógrafos', + numero: '150', + complemento: '', + }, + }); + + //FORNECEDORES + await prisma.fornecedor.create({ + data: { + email: 'acobraga@gmail.com.br', + telefone: '1128989475', + contaTipo: 'Juridica', + nomeFantasia: 'Aços Bragança', + cnpj: '53450617000175', + razaoSocial: 'Aços Bragança', + pais: 'Brasil', + cep: '07082600', + estado: 'SP', + cidade: 'Guarulhos', + bairro: 'Jardim City', + rua: 'Rua Dirceu Rocha Dias', + numero: '701', + complemento: '', + }, + }); + + await prisma.fornecedor.create({ + data: { + email: 'anfer@gmail.com.br', + telefone: '1726956067', + contaTipo: 'Juridica', + nomeFantasia: 'Anfer', + cnpj: '80481939000154', + razaoSocial: 'Anfer', + pais: 'Brasil', + cep: '15062006', + estado: 'SP', + cidade: 'São José do Rio Preto', + bairro: 'Chácara Jockey Club (Zona Rural)', + rua: 'Estrada Nelson Vitalino', + numero: '240', + complemento: '', + }, + }); + + await prisma.fornecedor.create({ + data: { + email: 'ponto.serra@gmail.com.br', + telefone: '1126784199', + contaTipo: 'Juridica', + nomeFantasia: 'Ponto Serra', + cnpj: '87331592000102', + razaoSocial: 'Ponto Serra', + pais: 'Brasil', + cep: '07830460', + estado: 'SP', + cidade: 'Franco da Rocha', + bairro: 'Vila Cariri', + rua: 'Estrada Sete Voltas', + numero: '123', + complemento: '', + }, + }); + + await prisma.fornecedor.create({ + data: { + email: 'baurufer@gmail.com.br', + telefone: '1139737192', + contaTipo: 'Juridica', + nomeFantasia: 'Baurufer', + cnpj: '36314123000144', + razaoSocial: 'Baurufer', + pais: 'Brasil', + cep: '01005020', + estado: 'SP', + cidade: 'São Paulo', + bairro: 'Chico de Paula', + rua: 'Rua São Francisco', + numero: '850', + complemento: '', + }, + }); + + await prisma.fornecedor.create({ + data: { + email: 'destak@gmail.com.br', + telefone: '1429037423', + contaTipo: 'Juridica', + nomeFantasia: 'Destak', + cnpj: '87058422000104', + razaoSocial: 'Destak', + pais: 'Brasil', + cep: '17066590', + estado: 'SP', + cidade: 'Bauru', + bairro: 'Parque Jaraguá', + rua: 'Rua Professor Ayrton Busch', + numero: '275', + complemento: '', + }, + }); + + await prisma.fornecedor.create({ + data: { + email: 'joseense@gmail.com.br', + telefone: '1137497095', + contaTipo: 'Juridica', + nomeFantasia: 'Aços Joseense', + cnpj: '47806122000133', + razaoSocial: 'Aços Joseense', + pais: 'Brasil', + cep: '05545200', + estado: 'SP', + cidade: 'São Paulo', + bairro: 'Jardim Monte Alegre', + rua: 'Rua Deolicio Alves de Souza', + numero: '755', + complemento: '', + }, + }); + + //ORÇAMENTOS + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 1500, + totalMateriais: 3470, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: 'Mesa de 3 x 3 x 3', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 3000, + totalMateriais: 3223.5, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 600, + totalMateriais: 1320, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 1905, + totalMateriais: 1330, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 7000, + totalMateriais: 5600, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 14000, + totalMateriais: 17224.9, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 710, + totalMateriais: 890.2, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 1260, + totalMateriais: 990, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 5432, + totalMateriais: 2345, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 3455, + totalMateriais: 1200.5, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 504, + totalMateriais: 943.5, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + await prisma.orcamento.create({ + data: { + validade: faker.date.future(), + dataOrcamento: faker.date.recent(), + totalMaoObra: 253, + totalMateriais: 449.7, + status: faker.helpers.arrayElement([ + 'Pendente', + 'Em_Processo', + 'Concluido', + ]), + prazoEstimadoProducao: faker.number.int({ min: 1, max: 30 }), + observacoes: '', + idCliente: faker.number.int({ min: 1, max: 6 }), // Gere um ID de cliente aleatório + }, + }); + + let nextOrcamentoId = 1; // Inicializa o próximo ID de orçamento + + function generateUniqueOrcamentoId() { + const id = nextOrcamentoId; + nextOrcamentoId++; // Aumenta o próximo ID para a próxima chamada + return id; + } + + //PEDIDOS + for (let i = 0; i < 6; i++) { + await prisma.pedido.create({ + data: { + pagamento: faker.number.int({ min: 30, max: 20000 }), + status: faker.helpers.arrayElement(['Pendente']), + idOrcamento: generateUniqueOrcamentoId(), // Gere um ID de orçamento aleatório + }, + }); + } + + //PRODUTOS + await prisma.produto.create({ + data: { + titulo: 'Portão 2M x 3M', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 3, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'Portão 2M x 3M', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 3, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'QUADRO EM GRAU P/ VIDRO BANHEIRO', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 2, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'PRATELEIRA COZINHA', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 2, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'PINTURA ELETROSTÁTICA', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 1, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'SUPORTE P/ VINHO ( 24 GARRAFAS )', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 1, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'PINTURA ELETROSTÁTICA', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 1, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'SUPORTE P/ VINHO ( 24 GARRAFAS )', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 1, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'PINTURA ELETROSTÁTICA', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 4, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + await prisma.produto.create({ + data: { + titulo: 'SUPORTE P/ VINHO ( 24 GARRAFAS )', + quantidade: 1, + valorUnitario: 100, + observacoes: '', + idOrcamento: 3, + valorMaoDeObra: 50, + valorMaterial: 50, + }, + }); + + //COTAÇÕES + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 100, + + idFornecedor: 1, // Gere um ID de fornecedor aleatório + idVariante: 1, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 120, + + idFornecedor: 2, // Gere um ID de fornecedor aleatório + idVariante: 1, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 140, + + idFornecedor: 3, // Gere um ID de fornecedor aleatório + idVariante: 1, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 95, + + idFornecedor: 5, // Gere um ID de fornecedor aleatório + idVariante: 1, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 90, + + idFornecedor: 4, // Gere um ID de fornecedor aleatório + idVariante: 3, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 95, + + idFornecedor: 1, // Gere um ID de fornecedor aleatório + idVariante: 3, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 98, + + idFornecedor: 3, // Gere um ID de fornecedor aleatório + idVariante: 3, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 80, + + idFornecedor: 5, // Gere um ID de fornecedor aleatório + idVariante: 3, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 50, + + idFornecedor: 6, // Gere um ID de fornecedor aleatório + idVariante: 6, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 80, + + idFornecedor: 4, // Gere um ID de fornecedor aleatório + idVariante: 6, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 70, + + idFornecedor: 3, // Gere um ID de fornecedor aleatório + idVariante: 6, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 2, // Gere um ID de fornecedor aleatório + idVariante: 6, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 2, // Gere um ID de fornecedor aleatório + idVariante: 6, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 1, // Gere um ID de fornecedor aleatório + idVariante: 9, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 2, // Gere um ID de fornecedor aleatório + idVariante: 9, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 3, // Gere um ID de fornecedor aleatório + idVariante: 9, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 4, // Gere um ID de fornecedor aleatório + idVariante: 9, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 1, // Gere um ID de fornecedor aleatório + idVariante: 10, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 2, // Gere um ID de fornecedor aleatório + idVariante: 10, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 3, // Gere um ID de fornecedor aleatório + idVariante: 10, // Gere um ID de insumo aleatório + }, + }); + await prisma.cotacao.create({ + data: { + data: faker.date.past(), + valor: 60, + + idFornecedor: 4, // Gere um ID de fornecedor aleatório + idVariante: 10, // Gere um ID de insumo aleatório + }, + }); + + //LISTA INSUMOS + await prisma.listaInsumo.create({ + data: { + quantidade: faker.number.int({ min: 1, max: 6 }), + idProduto: faker.number.int({ min: 1, max: 6 }), // Gere um ID de produto aleatório + idVariante: 1, // Gere um ID de insumo aleatório + idCotacao: 1, // Gere um ID de cotação aleatório + valorUnitario: faker.number.int({ min: 50, max: 500 }), + }, + }); + await prisma.listaInsumo.create({ + data: { + quantidade: faker.number.int({ min: 1, max: 6 }), + idProduto: faker.number.int({ min: 1, max: 6 }), // Gere um ID de produto aleatório + idVariante: 1, // Gere um ID de insumo aleatório + idCotacao: 2, // Gere um ID de cotação aleatório + valorUnitario: faker.number.int({ min: 50, max: 500 }), + }, + }); + await prisma.listaInsumo.create({ + data: { + quantidade: faker.number.int({ min: 1, max: 6 }), + idProduto: faker.number.int({ min: 1, max: 6 }), // Gere um ID de produto aleatório + idVariante: 1, // Gere um ID de insumo aleatório + idCotacao: 3, // Gere um ID de cotação aleatório + valorUnitario: faker.number.int({ min: 50, max: 500 }), + }, + }); + await prisma.listaInsumo.create({ + data: { + quantidade: faker.number.int({ min: 1, max: 6 }), + idProduto: faker.number.int({ min: 1, max: 6 }), // Gere um ID de produto aleatório + idVariante: 6, // Gere um ID de insumo aleatório + idCotacao: 1, // Gere um ID de cotação aleatório + valorUnitario: faker.number.int({ min: 50, max: 500 }), + }, + }); + await prisma.listaInsumo.create({ + data: { + quantidade: faker.number.int({ min: 1, max: 6 }), + idProduto: faker.number.int({ min: 1, max: 6 }), // Gere um ID de produto aleatório + idVariante: 5, // Gere um ID de insumo aleatório + idCotacao: 2, // Gere um ID de cotação aleatório + valorUnitario: faker.number.int({ min: 50, max: 500 }), + }, + }); + + //PRODUTOS BASE + await prisma.produtoBase.create({ + data: { + titulo: 'Portão 2 x 2m', + observacoes: '', + }, + }); + + await prisma.produtoBase.create({ + data: { + titulo: 'Portão 3 x 3m', + observacoes: '', + }, + }); + + await prisma.produtoBase.create({ + data: { + titulo: 'Portão 4 x 4m', + observacoes: '', + }, + }); + + await prisma.produtoBase.create({ + data: { + titulo: 'Janela 1 x 2m', + observacoes: '', + }, + }); + + await prisma.produtoBase.create({ + data: { + titulo: 'Suporte para espelho 20 x 20cm', + observacoes: '', + }, + }); + + await prisma.produtoBase.create({ + data: { + titulo: 'telha 2 x 2', + observacoes: '', + }, + }); + + //INSUMOS BASE + for (let i = 0; i < 12; i++) { + await prisma.insumoProdutoBase.create({ + data: { + quantidade: faker.number.int({ min: 1, max: 6 }), + idProdutoBase: faker.number.int({ min: 1, max: 6 }), // Gere um ID de produto base aleatório + idVariante: faker.number.int({ min: 1, max: 12 }), // Gere um ID de insumo aleatório + }, + }); + } + + console.log('Dados populados com sucesso'); +} + +console.log('Populando a database...'); +seedDatabase() + .catch((error) => { + console.error('Erro ao popular o banco de dados:', error); + }) + .finally(async () => { + await prisma.$disconnect(); + console.log('Processo finalizado'); + }); diff --git a/src/app.controller.ts b/src/app.controller.ts index cce879e..ce36d52 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,5 +1,7 @@ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; +import { CurrentUser } from './auth/decorators/current-user.decorator'; +import { Usuario } from './modules/usuarios/entities/usuario.entity'; @Controller() export class AppController { @@ -9,4 +11,9 @@ export class AppController { getHello(): string { return this.appService.getHello(); } + + @Get('me') + getMe(@CurrentUser() usuario: Usuario): Usuario { + return usuario; + } } diff --git a/src/app.module.ts b/src/app.module.ts index 8b4f61d..c5eae1e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,23 +13,33 @@ import { ListaInsumosModule } from './modules/lista-insumos/lista-insumos.module import { UsuariosModule } from './modules/usuarios/usuarios.module'; import { ProdutosBaseModule } from './modules/produtos-base/produtos-base.module'; import { InsumosProdutosBaseModule } from './modules/insumos-produtos-base/insumos-produtos-base.module'; +import { VariantesModule } from './modules/variantes/variantes.module'; +import { PrismaModule } from './databases/prisma/prisma.module'; +import { AuthModule } from './auth/auth.module'; +import { APP_GUARD } from '@nestjs/core'; +import { JwtAuthGuard } from './auth/guards/jwt-auth.guard'; +import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ + ConfigModule.forRoot(), InsumosModule, CategoriasModule, FornecedoresModule, ClientesModule, OrcamentosModule, PedidosModule, - ProdutosModule, CotacoesModule, ListaInsumosModule, UsuariosModule, + VariantesModule, + PrismaModule, + ProdutosModule, ProdutosBaseModule, InsumosProdutosBaseModule, + AuthModule, ], controllers: [AppController], - providers: [AppService], + providers: [AppService, { provide: APP_GUARD, useClass: JwtAuthGuard }], }) export class AppModule {} diff --git a/src/app.service.ts b/src/app.service.ts index 927d7cc..bf53ed8 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { - return 'Hello World!'; + return process.env.NODE_ENV + ? String(process.env.NODE_ENV) + : 'Servidor rodando'; } } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..76313c7 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,25 @@ +import { + Controller, + Post, + HttpCode, + HttpStatus, + UseGuards, + Request, +} from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { LocalAuthGuard } from './guards/local-auth.guard'; +import { AuthRequest } from './models/AuthRequest'; +import { IsPublic } from './decorators/is-public.decorator'; + +@Controller() +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @IsPublic() + @UseGuards(LocalAuthGuard) + @Post('login') + @HttpCode(HttpStatus.OK) + async login(@Request() req: AuthRequest) { + return await this.authService.login(req.user); + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..9378f12 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,25 @@ +import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { UsuariosModule } from 'src/modules/usuarios/usuarios.module'; +import { LocalStrategy } from './strategies/local.strategy'; +import { JwtModule } from '@nestjs/jwt/dist'; +import { JwtStrategy } from './strategies/jwt.strategy'; +import { LoginValidationMiddleware } from './middlewares/login-validation.middleware'; + +@Module({ + imports: [ + UsuariosModule, + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '30y' }, + }), + ], + controllers: [AuthController], + providers: [AuthService, LocalStrategy, JwtStrategy], +}) +export class AuthModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(LoginValidationMiddleware).forRoutes('login'); + } +} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..a36eb93 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { UsuariosService } from 'src/modules/usuarios/usuarios.service'; +import * as bcrypt from 'bcrypt'; +import { Usuario } from 'src/modules/usuarios/entities/usuario.entity'; +import { UsuarioPayload } from './models/UsuarioPayload'; +import { JwtService } from '@nestjs/jwt'; +import { UsuarioToken } from './models/UsuarioToken'; + +@Injectable() +export class AuthService { + constructor( + private readonly usuariosService: UsuariosService, + private readonly jwtService: JwtService, + ) {} + + async login(usuario: Usuario): Promise { + const payload: UsuarioPayload = { + sub: usuario.id, + email: usuario.email, + nome: usuario.nome, + }; + + const jwtToken = this.jwtService.sign(payload); + + return { accessToken: jwtToken, usuario: usuario }; + } + + async validateUser( + email: string, + password: string, + ): Promise { + const usuario = await this.usuariosService.findByEmail(email); + if (usuario) { + const isPasswordValid = await bcrypt.compare(password, usuario.senha); + if (isPasswordValid) { + usuario.senha = undefined; + return usuario; + } + throw new Error('O Email ou senha informado não esta correto'); + } + + throw new Error('O Email ou senha informado não esta correto'); + } +} diff --git a/src/auth/decorators/current-user.decorator.ts b/src/auth/decorators/current-user.decorator.ts new file mode 100644 index 0000000..ae299e4 --- /dev/null +++ b/src/auth/decorators/current-user.decorator.ts @@ -0,0 +1,11 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { AuthRequest } from '../models/AuthRequest'; +import { Usuario } from 'src/modules/usuarios/entities/usuario.entity'; + +export const CurrentUser = createParamDecorator( + (data: unknown, context: ExecutionContext): Usuario => { + const request = context.switchToHttp().getRequest(); + + return request.user; + }, +); diff --git a/src/auth/decorators/is-public.decorator.ts b/src/auth/decorators/is-public.decorator.ts new file mode 100644 index 0000000..999f6f3 --- /dev/null +++ b/src/auth/decorators/is-public.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; +export const IsPublic = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/src/auth/guards/jwt-auth.guard.ts b/src/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..a905f82 --- /dev/null +++ b/src/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,41 @@ +// NestJS +import { + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +// Password +import { AuthGuard } from '@nestjs/passport'; +// Decorators +import { IS_PUBLIC_KEY } from '../decorators/is-public.decorator'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor(private reflector: Reflector) { + super(); + } + + canActivate(context: ExecutionContext): Promise | boolean { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + + if (isPublic) { + return true; + } + + const canActivate = super.canActivate(context); + + if (typeof canActivate === 'boolean') { + return canActivate; + } + + const canActivatePromise = canActivate as Promise; + + return canActivatePromise.catch((error) => { + throw new UnauthorizedException(error.message); + }); + } +} diff --git a/src/auth/guards/local-auth.guard.ts b/src/auth/guards/local-auth.guard.ts new file mode 100644 index 0000000..c389c44 --- /dev/null +++ b/src/auth/guards/local-auth.guard.ts @@ -0,0 +1,21 @@ +import { + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class LocalAuthGuard extends AuthGuard('local') { + canActivate(context: ExecutionContext) { + return super.canActivate(context); + } + + handleRequest(err, usuario) { + if (err || !usuario) { + throw new UnauthorizedException(err?.message); + } + + return usuario; + } +} diff --git a/src/auth/middlewares/login-validation.middleware.ts b/src/auth/middlewares/login-validation.middleware.ts new file mode 100644 index 0000000..125c491 --- /dev/null +++ b/src/auth/middlewares/login-validation.middleware.ts @@ -0,0 +1,31 @@ +import { + BadRequestException, + Injectable, + NestMiddleware, +} from '@nestjs/common'; +import { NextFunction, Request, Response } from 'express'; +import { LoginDto } from '../models/Login.dto'; +import { validate } from 'class-validator'; + +@Injectable() +export class LoginValidationMiddleware implements NestMiddleware { + async use(req: Request, res: Response, next: NextFunction) { + const body = req.body; + + const loginRequestBody = new LoginDto(); + loginRequestBody.email = body.email; + loginRequestBody.password = body.password; + + const validations = await validate(loginRequestBody); + + if (validations.length) { + throw new BadRequestException( + validations.reduce((acc, curr) => { + return [...acc, ...Object.values(curr.constraints)]; + }, []), + ); + } + + next(); + } +} diff --git a/src/auth/models/AuthRequest.ts b/src/auth/models/AuthRequest.ts new file mode 100644 index 0000000..c3384c8 --- /dev/null +++ b/src/auth/models/AuthRequest.ts @@ -0,0 +1,6 @@ +import { Request } from 'express'; +import { Usuario } from 'src/modules/usuarios/entities/usuario.entity'; + +export interface AuthRequest extends Request { + user: Usuario; +} diff --git a/src/auth/models/Login.dto.ts b/src/auth/models/Login.dto.ts new file mode 100644 index 0000000..1d3893d --- /dev/null +++ b/src/auth/models/Login.dto.ts @@ -0,0 +1,9 @@ +import { IsEmail, IsString } from 'class-validator'; + +export class LoginDto { + @IsEmail() + email: string; + + @IsString() + password: string; +} diff --git a/src/auth/models/UsuarioFromJwt.ts b/src/auth/models/UsuarioFromJwt.ts new file mode 100644 index 0000000..499f0de --- /dev/null +++ b/src/auth/models/UsuarioFromJwt.ts @@ -0,0 +1,5 @@ +export interface UsuarioFromJwt { + id: number; + email: string; + nome: string; +} diff --git a/src/auth/models/UsuarioPayload.ts b/src/auth/models/UsuarioPayload.ts new file mode 100644 index 0000000..75c8494 --- /dev/null +++ b/src/auth/models/UsuarioPayload.ts @@ -0,0 +1,7 @@ +export interface UsuarioPayload { + sub: number; + email: string; + nome: string; + iat?: number; + exp?: number; +} diff --git a/src/auth/models/UsuarioToken.ts b/src/auth/models/UsuarioToken.ts new file mode 100644 index 0000000..5e74ff2 --- /dev/null +++ b/src/auth/models/UsuarioToken.ts @@ -0,0 +1,6 @@ +import { Usuario } from "src/modules/usuarios/entities/usuario.entity"; + +export interface UsuarioToken { + accessToken: string; + usuario: Usuario; +} diff --git a/src/auth/strategies/jwt.strategy.ts b/src/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..3591f11 --- /dev/null +++ b/src/auth/strategies/jwt.strategy.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UsuarioFromJwt } from '../models/UsuarioFromJwt'; +import { UsuarioPayload } from '../models/UsuarioPayload'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: process.env.JWT_SECRET, + }); + } + + async validate(payload: UsuarioPayload): Promise { + return { + id: payload.sub, + email: payload.email, + nome: payload.nome, + }; + } +} diff --git a/src/auth/strategies/local.strategy.ts b/src/auth/strategies/local.strategy.ts new file mode 100644 index 0000000..694e02f --- /dev/null +++ b/src/auth/strategies/local.strategy.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy } from 'passport-local'; +import { AuthService } from '../auth.service'; + +@Injectable() +export class LocalStrategy extends PassportStrategy(Strategy) { + constructor(private authService: AuthService) { + super({ usernameField: 'email' }); + } + + async validate(email: string, password: string) { + const usuario = await this.authService.validateUser(email, password); + return usuario; + } +} diff --git a/src/databases/prisma.service.ts b/src/databases/prisma.service.ts deleted file mode 100644 index bfbae30..0000000 --- a/src/databases/prisma.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable, OnModuleInit } from "@nestjs/common"; -import { PrismaClient } from "@prisma/client"; - -@Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit { - async onModuleInit() { - await this.$connect(); - } -} \ No newline at end of file diff --git a/src/databases/prisma/prisma.module.ts b/src/databases/prisma/prisma.module.ts new file mode 100644 index 0000000..ec0ce32 --- /dev/null +++ b/src/databases/prisma/prisma.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/src/databases/prisma/prisma.service.ts b/src/databases/prisma/prisma.service.ts new file mode 100644 index 0000000..359f950 --- /dev/null +++ b/src/databases/prisma/prisma.service.ts @@ -0,0 +1,9 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit { + async onModuleInit() { + await this.$connect(); + } +} diff --git a/src/main.ts b/src/main.ts index 11bf766..8786265 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,20 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + }), + ); + const config = new DocumentBuilder() .setTitle('Documentação com Swagger - Fábrica de Sinapse') .setDescription( @@ -28,8 +37,8 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); - - await app.listen(process.env.PORT ? Number(process.env.PORT) : 3333); + + await app.listen(process.env.PORT ? Number(process.env.PORT) : 3000); } bootstrap(); diff --git a/src/modules/categorias/categorias.controller.ts b/src/modules/categorias/categorias.controller.ts index 3b4d9c4..40e51f4 100644 --- a/src/modules/categorias/categorias.controller.ts +++ b/src/modules/categorias/categorias.controller.ts @@ -9,13 +9,14 @@ import { UsePipes, ValidationPipe, Query, + Header, + Res, } from '@nestjs/common'; import { CategoriasService } from './categorias.service'; import { CreateCategoriaDto } from './dto/create-categoria.dto'; import { UpdateCategoriaDto } from './dto/update-categoria.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from 'express'; @ApiTags('categorias') @@ -23,50 +24,52 @@ import { response as res } from 'express'; export class CategoriasController { constructor(private readonly categoriasService: CategoriasService) {} - @Get('paginate') - async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.categoriasService.countAllCategorias(); - - res.set('x-total-count', totalcount.toString()); - return await this.categoriasService.findAllWithPagination(page, Number(perPage)); -} - @Get('count') - countAll() { - return this.categoriasService.countAllCategorias(); + async countAll() { + return await this.categoriasService.countAllCategorias(); } - @UsePipes(ValidationPipe) + @Post() - create(@Body() createCategoriaDto: CreateCategoriaDto) { - return this.categoriasService.create(createCategoriaDto); + async create(@Body() createCategoriaDto: CreateCategoriaDto) { + return await this.categoriasService.create(createCategoriaDto); } @Get() - findAll() { - return this.categoriasService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('page') page: number,@Query('perPage') perPage: number,@Query('titulo_like') titulo_like : string, @Res({ passthrough: true }) res) { + page = page || 1; + perPage = perPage || await this.countAll(); + const categorias = await this.categoriasService.findAllWithPagination( + page, + Number(perPage), + titulo_like + ); + const total = await this.categoriasService.countAllCategorias(titulo_like); + res.header('x-total-count',total); + return await categorias } + @Get(':id') async findOne(@Param('id') id: string) { return await this.categoriasService.findOne(+id); } @Get(':busca') - findManyByTitle(@Param('busca') buscaParam: string) { - return this.categoriasService.findManyByTitle(buscaParam); + async findManyByTitle(@Param('busca') buscaParam: string) { + return await this.categoriasService.findManyByTitle(buscaParam); } - @UsePipes(ValidationPipe) + @Patch(':id') - update( + async update( @Param('id') id: string, @Body() updateCategoriaDto: UpdateCategoriaDto, ) { - return this.categoriasService.update(+id, updateCategoriaDto); + return await this.categoriasService.update(+id, updateCategoriaDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.categoriasService.remove(+id); + async remove(@Param('id') id: string) { + return await this.categoriasService.remove(+id); } } diff --git a/src/modules/categorias/categorias.module.ts b/src/modules/categorias/categorias.module.ts index b6a6ce1..c0de41b 100644 --- a/src/modules/categorias/categorias.module.ts +++ b/src/modules/categorias/categorias.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { CategoriasService } from './categorias.service'; import { CategoriasController } from './categorias.controller'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports:[PrismaModule], controllers: [CategoriasController], - providers: [CategoriasService, PrismaService], + providers: [CategoriasService], }) export class CategoriasModule {} diff --git a/src/modules/categorias/categorias.service.ts b/src/modules/categorias/categorias.service.ts index 36dff85..e1a12b2 100644 --- a/src/modules/categorias/categorias.service.ts +++ b/src/modules/categorias/categorias.service.ts @@ -1,32 +1,46 @@ import { Injectable } from '@nestjs/common'; import { CreateCategoriaDto } from './dto/create-categoria.dto'; import { UpdateCategoriaDto } from './dto/update-categoria.dto'; -import { PrismaService } from '../../databases/prisma.service'; - +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { Categoria } from './entities/categoria.entity'; @Injectable() export class CategoriasService { constructor(private readonly prismaService: PrismaService) {} - - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination(page: number, perPage: number, titulo_like? : string) { const skip = (page - 1) * perPage; - const categorias = await this.prismaService.categoria.findMany({ - skip, - take: perPage, - }); - const total = await this.prismaService.categoria.count(); - return { categorias }; + let categorias = Categoria[""]; + if(titulo_like){ + categorias = await this.prismaService.categoria.findMany({ + skip, + take: perPage, + where:{ + OR: [{ titulo: { contains: titulo_like, mode: 'insensitive' } }, + { tipo: { contains: titulo_like , mode: 'insensitive'} },], + }, + }); + }else{ + categorias = await this.prismaService.categoria.findMany({ + skip, + take: perPage, + }); + } + return categorias ; } - async findOneByTitle(titulo: string) { + async findOneByTitle(titulo: string ) { return await this.prismaService.categoria.findFirst({ where: { titulo }, }); } - async countAllCategorias(){ + async countAllCategorias(titulo_like : string = '') { return await this.prismaService.categoria.count({ - }); + where:{ + OR: [{ titulo: { contains: titulo_like } }, + { tipo: { contains: titulo_like } },], + }, + }); } async findManyByTitle(titulo: string) { @@ -54,13 +68,32 @@ export class CategoriasService { } async update(id: number, updateCategoriaDto: UpdateCategoriaDto) { - return await this.prismaService.categoria.update({ - where: { id }, - data: updateCategoriaDto, - }); + const categoriaExists = await this.findOne(id); + if (categoriaExists) { + const categoriaRepeated = await this.prismaService.categoria.findFirst({ + where: { + titulo: updateCategoriaDto.titulo, + NOT: { + id: id, + }, + }, + }); + if (!categoriaRepeated) { + return await this.prismaService.categoria.update({ + where: { id }, + data: updateCategoriaDto, + }); + } + return { data: { message: 'Categoria com titulo repetido' } }; + } + return { data: { message: 'Categoria não existe' } }; } async remove(id: number) { - return await this.prismaService.categoria.delete({ where: { id } }); + const categoriaExists = await this.findOne(id); + if (categoriaExists) { + return await this.prismaService.categoria.delete({ where: { id } }); + } + return { data: { message: 'Categoria não existe' } }; } } diff --git a/src/modules/categorias/dto/create-categoria.dto.ts b/src/modules/categorias/dto/create-categoria.dto.ts index 84aab2f..34fadf4 100644 --- a/src/modules/categorias/dto/create-categoria.dto.ts +++ b/src/modules/categorias/dto/create-categoria.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator'; export class CreateCategoriaDto { @ApiProperty({ @@ -22,6 +22,7 @@ export class CreateCategoriaDto { description: 'A descrição serve para detalhar a categoria', example: 'Grupo de materiais de aço, ferro e aluminio', }) + @IsOptional() @IsString({message: 'A descrição deve ser uma string'}) descricao?: string; } diff --git a/src/modules/categorias/dto/update-categoria.dto.ts b/src/modules/categorias/dto/update-categoria.dto.ts index e7f0893..a30fc64 100644 --- a/src/modules/categorias/dto/update-categoria.dto.ts +++ b/src/modules/categorias/dto/update-categoria.dto.ts @@ -1,7 +1,7 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateCategoriaDto } from './create-categoria.dto'; import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator'; export class UpdateCategoriaDto extends PartialType(CreateCategoriaDto) { @ApiProperty({ @@ -24,6 +24,7 @@ export class UpdateCategoriaDto extends PartialType(CreateCategoriaDto) { description: 'A descrição serve para detalhar a categoria', example: 'Grupo de materiais de aço, ferro e aluminio', }) + @IsOptional() @IsString({message: 'A descrição deve ser uma string'}) descricao?: string; } diff --git a/src/modules/categorias/entities/categoria.entity.ts b/src/modules/categorias/entities/categoria.entity.ts index d9a4227..3ec1282 100644 --- a/src/modules/categorias/entities/categoria.entity.ts +++ b/src/modules/categorias/entities/categoria.entity.ts @@ -1,14 +1,7 @@ -import { IsInt, IsNotEmpty, IsString } from 'class-validator'; - export class Categoria { id: number; - @IsNotEmpty({message: 'O tipo não pode estar vázio'}) - @IsString({message: 'O tipo deve ser uma string'}) tipo: string; - @IsNotEmpty({message: 'O titulo não pode estar vázio'}) - @IsString({message: 'O titulo deve ser uma string'}) titulo: string; - @IsString({message: 'A descrição deve ser uma string'}) descricao?: string; createdAt: Date; updatedAt: Date; diff --git a/src/modules/clientes/clientes.controller.ts b/src/modules/clientes/clientes.controller.ts index 1f7fc20..95310d6 100644 --- a/src/modules/clientes/clientes.controller.ts +++ b/src/modules/clientes/clientes.controller.ts @@ -1,58 +1,62 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; +import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UsePipes, ValidationPipe, Header, Res } from '@nestjs/common'; import { ClientesService } from './clientes.service'; import { CreateClienteDto } from './dto/create-cliente.dto'; import { UpdateClienteDto } from './dto/update-cliente.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from 'express'; + @ApiTags('clientes') @Controller('clientes') export class ClientesController { constructor(private readonly clientesService: ClientesService) {} -@Get('paginate') -async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.clientesService.countAllCliente(); - res.set('x-total-count', totalcount.toString()); - return await this.clientesService.findAllWithPagination(page, perPage); -} @Get('count') - countAll(){ - return this.clientesService.countAllCliente(); + async countAll(){ + return await this.clientesService.countAllCliente(); } + @Post() - create(@Body() createClienteDto: CreateClienteDto) { - return this.clientesService.create(createClienteDto); + async create(@Body() createClienteDto: CreateClienteDto) { + return await this.clientesService.create(createClienteDto); } - @Get() - findAll() { - return this.clientesService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('page') page: number,@Query('perPage') perPage: number,@Query('nome_like') nome_like : string, @Res({ passthrough: true }) res) { + page = page||1; + perPage = perPage||await this.countAll(); + const clientes = await this.clientesService.findAllWithPagination( + page, + Number(perPage), + nome_like + ); + const total = await this.clientesService.countAllCliente(nome_like); + res.header('x-total-count',total); + return await clientes } @Get('buscar/:termo') async buscarCliente(@Param('termo') termo: string) { - return this.clientesService.findManyCliente(termo); + return await this.clientesService.findManyCliente(termo); } @Get(':id') - findOne(@Param('id') id: string) { - return this.clientesService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.clientesService.findOne(+id); } + @Patch(':id') - update(@Param('id') id: string, @Body() updateClienteDto: UpdateClienteDto) { - return this.clientesService.update(+id, updateClienteDto); + async update(@Param('id') id: string, @Body() updateClienteDto: UpdateClienteDto) { + return await this.clientesService.update(+id, updateClienteDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.clientesService.remove(+id); + async remove(@Param('id') id: string) { + return await this.clientesService.remove(+id); } } diff --git a/src/modules/clientes/clientes.module.ts b/src/modules/clientes/clientes.module.ts index 651bab2..efb6e87 100644 --- a/src/modules/clientes/clientes.module.ts +++ b/src/modules/clientes/clientes.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { ClientesService } from './clientes.service'; import { ClientesController } from './clientes.controller'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [ClientesController], - providers: [ClientesService,PrismaService], + providers: [ClientesService], }) export class ClientesModule {} diff --git a/src/modules/clientes/clientes.service.ts b/src/modules/clientes/clientes.service.ts index f383aa1..6e9e394 100644 --- a/src/modules/clientes/clientes.service.ts +++ b/src/modules/clientes/clientes.service.ts @@ -1,56 +1,145 @@ import { Injectable } from '@nestjs/common'; import { CreateClienteDto } from './dto/create-cliente.dto'; import { UpdateClienteDto } from './dto/update-cliente.dto'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { Cliente } from './entities/cliente.entity'; @Injectable() export class ClientesService { constructor(private readonly prismaService: PrismaService) {} - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination( + page: number, + perPage: number, + nome_like?: string, + ) { const skip = (page - 1) * perPage; - const clientes = await this.prismaService.cliente.findMany({ - skip, - take: perPage, - }); - return { clientes}; - } - - async findOneByCliente(nome: string, email: string, telefone: string) { - return await this.prismaService.cliente.findFirst({ - where: { nome, email, telefone }, - }); + let clientes = Cliente['']; + if (nome_like) { + clientes = await this.prismaService.cliente.findMany({ + skip, + take: perPage, + where: { + OR: [ + { nome: { contains: nome_like , mode: 'insensitive' } }, + { email: { contains: nome_like , mode: 'insensitive'} }, + { rua: { contains: nome_like , mode: 'insensitive'} }, + { nomeFantasia: { contains: nome_like, mode: 'insensitive' } }, + { razaoSocial: { contains: nome_like , mode: 'insensitive'} }, + ], + }, + }); + } else { + clientes = await this.prismaService.cliente.findMany({ + skip, + take: perPage, + }); + } + + return clientes; } + async findExistingCliente(id: number, termo: string) { + if (!termo === undefined) { + var emailExists = await this.prismaService.cliente.findUnique({ + where: { + email: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var cpfExists = await this.prismaService.cliente.findUnique({ + where: { + cpf: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var rgExists = await this.prismaService.cliente.findUnique({ + where: { + rg: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var cnpjExists = await this.prismaService.cliente.findUnique({ + where: { + cnpj: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var razaoExists = await this.prismaService.cliente.findUnique({ + where: { + razaoSocial: termo, + NOT: { + id: id, + }, + }, + }); + } + if ( + !emailExists && + !cpfExists && + !rgExists && + !cnpjExists && + !razaoExists + ) { + return await 0; + } + return await 1; + } async findManyCliente(termo: string) { return await this.prismaService.cliente.findMany({ where: { OR: [ - {nome: {contains: termo}}, - {email: {contains: termo}}, - {telefone: {contains: termo}}, + { nome: { contains: termo } }, + { email: { contains: termo } }, + { telefone: { contains: termo } }, ], }, }); } - async countAllCliente() { - return await this.prismaService.cliente.count({}); + async countAllCliente(nome_like: string = '') { + return await this.prismaService.cliente.count({ where: { + OR: [ + { nome: { contains: nome_like , mode: 'insensitive' } }, + { email: { contains: nome_like , mode: 'insensitive'} }, + { rua: { contains: nome_like , mode: 'insensitive'} }, + { nomeFantasia: { contains: nome_like, mode: 'insensitive' } }, + { razaoSocial: { contains: nome_like , mode: 'insensitive'} }, + ], + },}); } async create(createClienteDto: CreateClienteDto) { - const cliente = await this.findOneByCliente( - createClienteDto.nome, - createClienteDto.email, - createClienteDto.telefone, - ); - if (!cliente) { + if ( + !(await this.findExistingCliente(undefined, createClienteDto.email)) && + !(await this.findExistingCliente(undefined, createClienteDto.cpf)) && + !(await this.findExistingCliente(undefined, createClienteDto.rg)) && + !(await this.findExistingCliente(undefined, createClienteDto.cnpj)) && + !(await this.findExistingCliente(undefined, createClienteDto.razaoSocial)) + ) { return await this.prismaService.cliente.create({ data: createClienteDto, }); } - return { data: { message: 'Cliente ja cadastrado' } }; + + return { data: { message: 'Cliente com dados repetidos' } }; } async findAll() { @@ -62,13 +151,51 @@ export class ClientesService { } async update(id: number, updateClienteDto: UpdateClienteDto) { - return await this.prismaService.cliente.update({ - where: { id }, - data: updateClienteDto, - }); + const clienteExists = await this.findOne(id); + if (clienteExists) { + if ( + !(await this.findExistingCliente( + clienteExists.id, + updateClienteDto.email, + )) && + !(await this.findExistingCliente( + clienteExists.id, + updateClienteDto.cpf, + )) && + !(await this.findExistingCliente( + clienteExists.id, + updateClienteDto.rg, + )) && + !(await this.findExistingCliente( + clienteExists.id, + updateClienteDto.cnpj, + )) && + !(await this.findExistingCliente( + clienteExists.id, + updateClienteDto.razaoSocial, + )) + ) { + return await this.prismaService.cliente.update({ + where: { id }, + data: updateClienteDto, + }); + } + return { data: { message: 'Cliente com dados repetidos' } }; + } + return { data: { message: 'Cliente não existe' } }; } async remove(id: number) { - return await this.prismaService.cliente.delete({ where: { id } }); + const clienteExists = await this.findOne(id); + if (clienteExists) { + const clienteOrcs = await this.prismaService.orcamento.findFirst({ + where: { idCliente: id }, + }); + if (!clienteOrcs) { + return await this.prismaService.cliente.delete({ where: { id } }); + } + return { data: { message: 'Cliente possui orçamentos' } }; + } + return { data: { message: 'Cliente não existe' } }; } } diff --git a/src/modules/clientes/dto/create-cliente.dto.ts b/src/modules/clientes/dto/create-cliente.dto.ts index c592115..56915ae 100644 --- a/src/modules/clientes/dto/create-cliente.dto.ts +++ b/src/modules/clientes/dto/create-cliente.dto.ts @@ -1,106 +1,167 @@ import { ApiProperty } from '@nestjs/swagger'; import { contaTipo } from '@prisma/client'; +import { + IsEmail, + IsEnum, + IsNotEmpty, + IsNumberString, + IsOptional, + IsString, + Matches, + ValidateIf, +} from 'class-validator'; export class CreateClienteDto { @ApiProperty({ description: 'O email serve para descrever o email do cliente', example: 'email@gmail.com', }) + @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) + @IsEmail({}, { message: 'O e-mail inserido não é válido' }) email: string; @ApiProperty({ - description: 'O telefone serve para descrever o numero de telefone do cliente', + description: + 'O telefone serve para descrever o numero de telefone do cliente', example: '1734112736', }) + @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) + @IsNumberString({}, { message: 'O telefone inserido não é válido' }) telefone: string; @ApiProperty({ description: 'O tipo serve para diferenciar entre pessoa fisica e juridica', example: 'Fisica', }) + @IsNotEmpty({ message: 'O tipo da conta não pode estar vazio' }) + @IsEnum(contaTipo, { + message: 'O tipo da conta não condiz com as opções disponíveis', + }) contaTipo: contaTipo; @ApiProperty({ - description: 'O nome serve para identificar o cliente, caso seja pessoa fisica', + description: + 'O nome serve para identificar o cliente, caso seja pessoa fisica', example: 'João Pedro', }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsString({ message: 'O nome inserido não é válido' }) nome?: string; @ApiProperty({ - description: 'O CPF serve para identificar o cliente, caso seja pessoa fisica', + description: + 'O CPF serve para identificar o cliente, caso seja pessoa fisica', example: '02370334029', }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O CPF inserido não é válido' }) cpf?: string; @ApiProperty({ - description: 'O RG serve para identificar o cliente, caso seja pessoa fisica', + description: + 'O RG serve para identificar o cliente, caso seja pessoa fisica', example: '114421225', }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O RG inserido não é válido' }) rg?: string; @ApiProperty({ - description: 'O nome fantasia serve para identificar o cliente, caso seja pessoa juridica', + description: + 'O nome fantasia serve para identificar o cliente, caso seja pessoa juridica', example: 'ZawnTech', }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'O nome fantasia inserido não é válido' }) nomeFantasia?: string; @ApiProperty({ - description: 'A razão social serve para identificar o cliente, caso seja pessoa juridica', + description: + 'A razão social serve para identificar o cliente, caso seja pessoa juridica', example: 'Industria mecanica modelo Ltda.', }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'A razão social inserida não é válida' }) razaoSocial?: string; @ApiProperty({ - description: 'O CNPJ serve para identificar o cliente, caso seja pessoa juridica', + description: + 'O CNPJ serve para identificar o cliente, caso seja pessoa juridica', example: '31895255000193', }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsNumberString({}, { message: 'O CNPJ inserido não é válido' }) cnpj?: string; @ApiProperty({ - description: 'O pais serve para identificar a região onde o cliente se encontra', + description: + 'O pais serve para identificar a região onde o cliente se encontra', example: 'Brasil', }) + @IsOptional() + @IsString({ message: 'O país inserido não é válido' }) pais?: string; @ApiProperty({ - description: 'O CEP serve para identificar a região onde o cliente se encontra', + description: + 'O CEP serve para identificar a região onde o cliente se encontra', example: '69918170', }) + @IsOptional() + @IsNumberString({}, { message: 'O CEP inserido não é válido' }) cep?: string; @ApiProperty({ - description: 'O estado serve para identificar a região onde o cliente se encontra', + description: + 'O estado serve para identificar a região onde o cliente se encontra', example: 'SP', }) + @IsOptional() + @IsString({ message: 'O estado inserido não é válido' }) estado?: string; @ApiProperty({ - description: 'A cidade serve para identificar a região onde o cliente se encontra', + description: + 'A cidade serve para identificar a região onde o cliente se encontra', example: 'Sorocaba', }) + @IsOptional() + @IsString({ message: 'A cidade inserida não é válida' }) cidade?: string; @ApiProperty({ - description: 'O bairro serve para identificar o local onde o cliente se encontra', + description: + 'O bairro serve para identificar o local onde o cliente se encontra', example: 'Vila Barão', }) + @IsOptional() + @IsString({ message: 'O bairro inserido não é válido' }) bairro?: string; @ApiProperty({ - description: 'A rua serve para identificar o local onde o cliente se encontra', + description: + 'A rua serve para identificar o local onde o cliente se encontra', example: 'Rua Manuel Lourenço Rodrigues', }) + @IsOptional() + @IsString({ message: 'A rua inserida não é válida' }) rua?: string; @ApiProperty({ - description: 'O numero serve para identificar o local onde o cliente se encontra', + description: + 'O numero serve para identificar o local onde o cliente se encontra', example: '44', }) + @IsOptional() + @IsNumberString({}, { message: 'O numero inserido não é válido' }) numero?: string; @ApiProperty({ - description: 'O complemento serve para dar informações adicionais para identificar o local onde o cliente se encontra', + description: + 'O complemento serve para dar informações adicionais para identificar o local onde o cliente se encontra', example: 'apt. 42', }) + @IsOptional() + @IsString({ message: 'O complemento inserido não é válido' }) complemento?: string; } diff --git a/src/modules/clientes/dto/update-cliente.dto.ts b/src/modules/clientes/dto/update-cliente.dto.ts index a7e6ad6..2feeccf 100644 --- a/src/modules/clientes/dto/update-cliente.dto.ts +++ b/src/modules/clientes/dto/update-cliente.dto.ts @@ -2,107 +2,168 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateClienteDto } from './create-cliente.dto'; import { contaTipo } from '@prisma/client'; import { ApiProperty } from '@nestjs/swagger'; +import { + IsEmail, + IsEnum, + IsNotEmpty, + IsNumberString, + IsOptional, + IsString, + Matches, + ValidateIf, +} from 'class-validator'; export class UpdateClienteDto extends PartialType(CreateClienteDto) { @ApiProperty({ - description: 'O email serve pare descrever o email do cliente', + description: 'O email serve para descrever o email do cliente', example: 'email@gmail.com', }) + @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) + @IsEmail({}, { message: 'O e-mail inserido não é válido' }) email?: string; @ApiProperty({ - description: 'O telefone serve para descrever o numero de telefone do cliente', + description: + 'O telefone serve para descrever o numero de telefone do cliente', example: '1734112736', }) + @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) + @IsNumberString({}, { message: 'O telefone inserido não é válido' }) telefone?: string; @ApiProperty({ description: 'O tipo serve para diferenciar entre pessoa fisica e juridica', example: 'Fisica', }) + @IsNotEmpty({ message: 'O tipo da conta não pode estar vazio' }) + @IsEnum(contaTipo, { + message: 'O tipo da conta não condiz com as opções disponíveis', + }) contaTipo?: contaTipo; @ApiProperty({ - description: 'O nome serve para identificar o cliente, caso seja pessoa fisica', + description: + 'O nome serve para identificar o cliente, caso seja pessoa fisica', example: 'João Pedro', }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsString({ message: 'O nome inserido não é válido' }) nome?: string; @ApiProperty({ - description: 'O CPF serve para identificar o cliente, caso seja pessoa fisica', + description: + 'O CPF serve para identificar o cliente, caso seja pessoa fisica', example: '02370334029', }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O CPF inserido não é válido' }) cpf?: string; @ApiProperty({ - description: 'O RG serve para identificar o cliente, caso seja pessoa fisica', + description: + 'O RG serve para identificar o cliente, caso seja pessoa fisica', example: '114421225', }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O RG inserido não é válido' }) rg?: string; @ApiProperty({ - description: 'O nome fantasia serve para identificar o cliente, caso seja pessoa juridica', + description: + 'O nome fantasia serve para identificar o cliente, caso seja pessoa juridica', example: 'ZawnTech', }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'O nome fantasia inserido não é válido' }) nomeFantasia?: string; @ApiProperty({ - description: 'A razão social serve para identificar o cliente, caso seja pessoa juridica', + description: + 'A razão social serve para identificar o cliente, caso seja pessoa juridica', example: 'Industria mecanica modelo Ltda.', }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'A razão social inserida não é válida' }) razaoSocial?: string; @ApiProperty({ - description: 'O CNPJ serve para identificar o cliente, caso seja pessoa juridica', + description: + 'O CNPJ serve para identificar o cliente, caso seja pessoa juridica', example: '31895255000193', }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsNumberString({}, { message: 'O CNPJ inserido não é válido' }) cnpj?: string; @ApiProperty({ - description: 'O pais serve para identificar a região onde o cliente se encontra', + description: + 'O pais serve para identificar a região onde o cliente se encontra', example: 'Brasil', }) + @IsOptional() + @IsString({ message: 'O país inserido não é válido' }) pais?: string; @ApiProperty({ - description: 'O CEP serve para identificar a região onde o cliente se encontra', + description: + 'O CEP serve para identificar a região onde o cliente se encontra', example: '69918170', }) + @IsOptional() + @IsNumberString({}, { message: 'O CEP inserido não é válido' }) cep?: string; @ApiProperty({ - description: 'O estado serve para identificar a região onde o cliente se encontra', + description: + 'O estado serve para identificar a região onde o cliente se encontra', example: 'SP', }) + @IsOptional() + @IsString({ message: 'O estado inserido não é válido' }) estado?: string; @ApiProperty({ - description: 'A cidade serve para identificar a região onde o cliente se encontra', + description: + 'A cidade serve para identificar a região onde o cliente se encontra', example: 'Sorocaba', }) + @IsOptional() + @IsString({ message: 'A cidade inserida não é válida' }) cidade?: string; @ApiProperty({ - description: 'O bairro serve para identificar o local onde o cliente se encontra', + description: + 'O bairro serve para identificar o local onde o cliente se encontra', example: 'Vila Barão', }) + @IsOptional() + @IsString({ message: 'O bairro inserido não é válido' }) bairro?: string; @ApiProperty({ - description: 'A rua serve para identificar o local onde o cliente se encontra', + description: + 'A rua serve para identificar o local onde o cliente se encontra', example: 'Rua Manuel Lourenço Rodrigues', }) + @IsOptional() + @IsString({ message: 'A rua inserida não é válida' }) rua?: string; @ApiProperty({ - description: 'O numero serve para identificar o local onde o cliente se encontra', + description: + 'O numero serve para identificar o local onde o cliente se encontra', example: '44', }) + @IsOptional() + @IsNumberString({}, { message: 'O numero inserido não é válido' }) numero?: string; @ApiProperty({ - description: 'O complemento serve para dar informações adicionais para identificar o local onde o cliente se encontra', + description: + 'O complemento serve para dar informações adicionais para identificar o local onde o cliente se encontra', example: 'apt. 42', }) + @IsOptional() + @IsString({ message: 'O complemento inserido não é válido' }) complemento?: string; } diff --git a/src/modules/clientes/entities/cliente.entity.ts b/src/modules/clientes/entities/cliente.entity.ts index 99fca25..87729f0 100644 --- a/src/modules/clientes/entities/cliente.entity.ts +++ b/src/modules/clientes/entities/cliente.entity.ts @@ -1,34 +1,14 @@ import { contaTipo } from '@prisma/client'; -import { - IsDate, - IsEmail, - IsEnum, - IsNotEmpty, - IsString, - Matches, - ValidateIf, -} from 'class-validator'; export class Cliente { id: number; - @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) - @IsEmail({}, { message: 'O e-mail não é válido' }) email: string; - @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) - @IsString({ message: 'O telefone não é uma string' }) telefone: string; - @IsNotEmpty({ message: 'O tipo da conta não pode estar vazio' }) - @IsEnum(contaTipo, { - message: 'O tipo da conta não condiz com as opções disponíveis', - }) contaTipo: contaTipo; - @Matches(/^[a-zA-Z -]*$/, { message: 'O nome só pode ter letras' }) nome?: string; cpf?: string; rg?: string; - @IsString({ message: 'O nome fantasia não é uma string' }) nomeFantasia?: string; - @IsString({ message: 'A razão social não é uma string' }) razaoSocial?: string; cnpj?: string; pais?: string; diff --git a/src/modules/cotacoes/cotacoes.controller.spec.ts b/src/modules/cotacoes/cotacoes.controller.spec.ts index ab99d07..d11de12 100644 --- a/src/modules/cotacoes/cotacoes.controller.spec.ts +++ b/src/modules/cotacoes/cotacoes.controller.spec.ts @@ -1,17 +1,17 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { CotacaosController } from './cotacaos.controller'; -import { CotacaosService } from './cotacaos.service'; +import { CotacoesController } from './cotacoes.controller'; +import { CotacoesService } from './cotacoes.service'; describe('CotacaosController', () => { - let controller: CotacaosController; + let controller: CotacoesController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [CotacaosController], - providers: [CotacaosService], + controllers: [CotacoesController], + providers: [CotacoesService], }).compile(); - controller = module.get(CotacaosController); + controller = module.get(CotacoesController); }); it('should be defined', () => { diff --git a/src/modules/cotacoes/cotacoes.controller.ts b/src/modules/cotacoes/cotacoes.controller.ts index 83194d3..9658889 100644 --- a/src/modules/cotacoes/cotacoes.controller.ts +++ b/src/modules/cotacoes/cotacoes.controller.ts @@ -7,38 +7,64 @@ import { Param, Delete, Query, + UsePipes, + ValidationPipe, + Header, + Res, } from '@nestjs/common'; import { CotacoesService } from './cotacoes.service'; import { CreateCotacaoDto } from './dto/create-cotacao.dto'; import { UpdateCotacaoDto } from './dto/update-cotacao.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from "express"; +import { recotarDto } from './dto/recotar.dto'; @ApiTags('cotacoes') @Controller('cotacoes') export class CotacoesController { - constructor(private readonly cotacoesService: CotacoesService) {} + constructor(private readonly cotacoesService: CotacoesService) { } + + @Post('recotar/:id') + async recotar(@Param('id') idCotacao: string, @Body() recotarDto: recotarDto) { + return await this.cotacoesService.recotar(+idCotacao, recotarDto); + } + -@Get('paginate') -async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.cotacoesService.countAllCotacaos(); - res.set('x-total-count', totalcount.toString()); - return await this.cotacoesService.findAllWithPagination(page, perPage); -} @Get('count') - countAll() { - return this.cotacoesService.countAllCotacaos(); + async countAll() { + return await this.cotacoesService.countAllCotacaos(); } + + @Get('countbyId') + async countById(id:number) { + if(id){ + return await this.cotacoesService.countByIdInsumoCotacaos(id); + } + return await this.cotacoesService.countAllCotacaos(); + } + + @Post() - create(@Body() createCotacaoDto: CreateCotacaoDto) { - return this.cotacoesService.create(createCotacaoDto); + async create(@Body() createCotacaoDto: CreateCotacaoDto) { + return await this.cotacoesService.create(createCotacaoDto); } @Get() - findAll() { - return this.cotacoesService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('fornecedor') idFornecedor: number,@Query('insumo') idInsumo: number, @Query('page') page: number,@Query('perPage') perPage: number,@Query('nome_like') nome_like : string, @Res({ passthrough: true }) res) { + page = page||1; + perPage = perPage||await this.countAll(); + + const cotacoes = await this.cotacoesService.findAllWithPagination( + +idInsumo, + page, + Number(perPage), + nome_like, + +idFornecedor + ); + const total = await this.countById(+idInsumo); + res.header('x-total-count',total); + return await cotacoes } @Get(':id') @@ -46,14 +72,15 @@ async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPa return await this.cotacoesService.findOne(+id); } + @Patch(':id') - update(@Param('id') id: string, @Body() updateCotacaoDto: UpdateCotacaoDto) { - return this.cotacoesService.update(+id, updateCotacaoDto); + async update(@Param('id') id: string, @Body() updateCotacaoDto: UpdateCotacaoDto) { + return await this.cotacoesService.update(+id, updateCotacaoDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.cotacoesService.remove(+id); + async remove(@Param('id') id: string) { + return await this.cotacoesService.remove(+id); } @Get('findByFornecedor/:id') diff --git a/src/modules/cotacoes/cotacoes.module.ts b/src/modules/cotacoes/cotacoes.module.ts index d23e4b3..654c356 100644 --- a/src/modules/cotacoes/cotacoes.module.ts +++ b/src/modules/cotacoes/cotacoes.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { CotacoesService } from './cotacoes.service'; import { CotacoesController } from './cotacoes.controller'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [CotacoesController], - providers: [CotacoesService, PrismaService], + providers: [CotacoesService], + exports: [CotacoesService], }) export class CotacoesModule {} diff --git a/src/modules/cotacoes/cotacoes.service.ts b/src/modules/cotacoes/cotacoes.service.ts index 23456c3..ddbc2f2 100644 --- a/src/modules/cotacoes/cotacoes.service.ts +++ b/src/modules/cotacoes/cotacoes.service.ts @@ -1,27 +1,126 @@ import { Injectable } from '@nestjs/common'; import { CreateCotacaoDto } from './dto/create-cotacao.dto'; import { UpdateCotacaoDto } from './dto/update-cotacao.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { recotarDto } from './dto/recotar.dto'; +import { Cotacao } from './entities/cotacao.entity'; @Injectable() export class CotacoesService { constructor(private readonly prismaService: PrismaService) {} - async findAllWithPagination(page: number, perPage: number) { + async recotar(idCotacao: number, recotarDto: recotarDto) { + const oldQuotation = await this.prismaService.cotacao.update({ + where: { id: idCotacao }, + data: { + obsoleta: true, + }, + }); + if (!oldQuotation) { + return { data: { message: 'Não foi possivel criar a nova cotação' } }; + } + const newQuotation = await this.prismaService.cotacao.create({ + data: { + idVariante: oldQuotation.idVariante, + idFornecedor: oldQuotation.idFornecedor, + valor: recotarDto.valor, + data: recotarDto.data, + }, + }); + if (!newQuotation) { + return { data: { message: 'Não foi possivel criar a nova cotação' } }; + } + + return newQuotation; + } + + async findAllWithPagination( + id: number, + page: number, + perPage: number, + nome_like?: string, + idfornecedor?: number, + ) { const skip = (page - 1) * perPage; - const cotacoes = await this.prismaService.cotacao.findMany({ - skip, - take: perPage, - }); - return { cotacoes }; + let cotacoes = Cotacao['']; + + if (id && idfornecedor) { + cotacoes = await this.prismaService.cotacao.findMany({ + skip, + take: perPage, + where: { + idVariante: id, + idFornecedor: idfornecedor, + obsoleta:false, + OR: [ + { variante: { insumo: { titulo: { contains: nome_like, mode: 'insensitive' } } } }, + { fornecedor: { nome: { contains: nome_like, mode: 'insensitive' } } }, + { fornecedor: { nomeFantasia: { contains: nome_like, mode: 'insensitive' } } }, + { fornecedor: { razaoSocial: { contains: nome_like, mode: 'insensitive' } } }, + ], + }, + }); + } else if (id) { + cotacoes = await this.prismaService.cotacao.findMany({ + skip, + take: perPage, + where: { + idVariante: id, + obsoleta:false, + OR: [ + { variante: { insumo: { titulo: { contains: nome_like, mode: 'insensitive' } } } }, + { fornecedor: { nome: { contains: nome_like, mode: 'insensitive' } } }, + { fornecedor: { nomeFantasia: { contains: nome_like, mode: 'insensitive' } } }, + { fornecedor: { razaoSocial: { contains: nome_like, mode: 'insensitive' } } }, + ], + }, + }); + } else { + cotacoes = await this.prismaService.cotacao.findMany({ + skip, + take: perPage, + where: { + OR: [ + { variante: { insumo: { titulo: { contains: nome_like, mode: 'insensitive' } } } }, + { fornecedor: { nome: { contains: nome_like, mode: 'insensitive' } } }, + { fornecedor: { nomeFantasia: { contains: nome_like , mode: 'insensitive'} } }, + { fornecedor: { razaoSocial: { contains: nome_like , mode: 'insensitive'} } }, + ], + }, + }); + } + + return cotacoes; } + async countAllCotacaos() { return await this.prismaService.cotacao.count({}); } + + async countByIdInsumoCotacaos(id: number) { + return await this.prismaService.cotacao.count({ + where: { + idVariante: id, + }, + }); + } + async create(createCotacaoDto: CreateCotacaoDto) { - return await this.prismaService.cotacao.create({ - data: createCotacaoDto, + const fornecedorExists = await this.prismaService.fornecedor.findFirst({ + where: { id: createCotacaoDto.idFornecedor }, }); + if (fornecedorExists) { + const insumoExists = await this.prismaService.variante.findFirst({ + where: { id: createCotacaoDto.idVariante }, + }); + if (insumoExists) { + return await this.prismaService.cotacao.create({ + data: createCotacaoDto, + }); + } + return { data: { message: 'Insumo não existe' } }; + } + return { data: { message: 'Fornecedor não existe' } }; } async findAll() { @@ -36,7 +135,7 @@ export class CotacoesService { async findManyByInsumo(id: number) { return await this.prismaService.cotacao.findMany({ - where: { idInsumo: id }, + where: { idVariante: id }, }); } @@ -45,17 +144,42 @@ export class CotacoesService { where: { id, }, + }); } async update(id: number, updateCotacaoDto: UpdateCotacaoDto) { - return this.prismaService.cotacao.update({ - where: { id }, - data: updateCotacaoDto, - }); + const cotacaoExists = await this.findOne(1); + if (cotacaoExists) { + const notObsolete = await this.findOne(id); + if (notObsolete.obsoleta === false) { + const fornecedorExists = await this.prismaService.fornecedor.findFirst({ + where: { id: updateCotacaoDto.idFornecedor }, + }); + if (fornecedorExists) { + const insumoExists = await this.prismaService.variante.findFirst({ + where: { id: updateCotacaoDto.idVariante }, + }); + if (insumoExists) { + return await this.prismaService.cotacao.update({ + where: { id }, + data: updateCotacaoDto, + }); + } + return { data: { message: 'Insumo não existe' } }; + } + return { data: { message: 'Fornecedor não existe' } }; + } + return { data: { message: 'Cotação selecionada é obsoleta' } }; + } + return { data: { message: 'Cotação não existe' } }; } async remove(id: number) { - return await this.prismaService.cotacao.delete({ where: { id } }); + const cotacaoExists = await this.findOne(id); + if (cotacaoExists) { + return await this.prismaService.cotacao.delete({ where: { id } }); + } + return { data: { message: 'Cotação não existe' } }; } } diff --git a/src/modules/cotacoes/dto/create-cotacao.dto.ts b/src/modules/cotacoes/dto/create-cotacao.dto.ts index e5fe079..506833a 100644 --- a/src/modules/cotacoes/dto/create-cotacao.dto.ts +++ b/src/modules/cotacoes/dto/create-cotacao.dto.ts @@ -1,4 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreateCotacaoDto { @ApiProperty({ @@ -6,6 +7,8 @@ export class CreateCotacaoDto { 'A data serve para descrever quando esta cotação foi realizada', example: '2023-10-23T17:30:44.382Z', }) + @IsNotEmpty({ message: 'A data não pode estar vazia' }) + @IsDateString({},{ message: 'A data inserida não é válida' }) data: Date; @ApiProperty({ @@ -13,13 +16,17 @@ export class CreateCotacaoDto { 'O valor serve para descrever o quanto o insumo de uma cotação especifica esta custando', example: '100', }) + @IsNotEmpty({ message: 'O valor não pode estar vazio' }) + @IsNumber({}, { message: 'O valor inserido não é válido' }) valor: number; @ApiProperty({ description: 'O id do fornecedor serve para descrever com qual fornecedor foi realizada a cotação', - example: 'NK Serralheria', + example: '1', }) + @IsNumber({}, { message: 'O fornecedor inserido não é válido' }) + @IsNotEmpty({ message: 'O fornecedor não pode estar vazio' }) idFornecedor: number; @ApiProperty({ @@ -27,7 +34,9 @@ export class CreateCotacaoDto { 'O id do insumo serve para descrever para qual insumo esta cotação foi realizada', example: '1', }) - idInsumo: number; - - unidade: string; + @IsNotEmpty({ message: 'O insumo não pode estar vazio' }) + @IsNumber({}, { message: 'O insumo inserido não é válido' }) + idVariante: number; + + } diff --git a/src/modules/cotacoes/dto/recotar.dto.ts b/src/modules/cotacoes/dto/recotar.dto.ts new file mode 100644 index 0000000..1c708db --- /dev/null +++ b/src/modules/cotacoes/dto/recotar.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class recotarDto { + @IsNotEmpty({ message: 'A data não pode estar vazia' }) + @IsDateString({}, { message: 'A data inserida não é válida' }) + data: Date; + + @ApiProperty({ + description: + 'O valor serve para descrever o quanto o insumo de uma cotação especifica esta custando', + example: '100', + }) + @IsNotEmpty({ message: 'O valor não pode estar vazio' }) + @IsNumber({}, { message: 'O valor inserido não é válido' }) + valor: number; +} diff --git a/src/modules/cotacoes/dto/update-cotacao.dto.ts b/src/modules/cotacoes/dto/update-cotacao.dto.ts index 55772cf..3e2ca20 100644 --- a/src/modules/cotacoes/dto/update-cotacao.dto.ts +++ b/src/modules/cotacoes/dto/update-cotacao.dto.ts @@ -1,6 +1,14 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateCotacaoDto } from './create-cotacao.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { + IsBoolean, + IsDateString, + IsNotEmpty, + IsNumber, + IsString, + ValidateIf, +} from 'class-validator'; export class UpdateCotacaoDto extends PartialType(CreateCotacaoDto) { @ApiProperty({ @@ -8,20 +16,35 @@ export class UpdateCotacaoDto extends PartialType(CreateCotacaoDto) { 'A data serve para descrever quando esta cotação foi realizada', example: '2023-10-23T17:30:44.382Z', }) + @IsNotEmpty({ message: 'A data não pode estar vazia' }) + @IsDateString({}, { message: 'A data inserida não é válida' }) data?: Date; + @ApiProperty({ + description: + 'A propriedade obsoleta serve para descrever quando esta cotação deixou de ser atualizada', + example: 'false', + }) + @ValidateIf((object, value) => value !== undefined) + @IsBoolean({ message: 'O valor de obsoleta inserido não é válido' }) + obsoleta?: boolean; + @ApiProperty({ description: 'O valor serve para descrever o quanto o insumo de uma cotação especifica esta custando', example: '100', }) + @IsNotEmpty({ message: 'O valor não pode estar vazio' }) + @IsNumber({}, { message: 'O valor inserido não é válido' }) valor?: number; @ApiProperty({ description: 'O id do fornecedor serve para descrever com qual fornecedor foi realizada a cotação', - example: 'NK Serralheria', + example: '1', }) + @IsNumber({}, { message: 'O fornecedor inserido não é válido' }) + @IsNotEmpty({ message: 'O fornecedor não pode estar vazio' }) idFornecedor?: number; @ApiProperty({ @@ -29,7 +52,9 @@ export class UpdateCotacaoDto extends PartialType(CreateCotacaoDto) { 'O id do insumo serve para descrever para qual insumo esta cotação foi realizada', example: '1', }) - idInsumo?: number; + @IsNotEmpty({ message: 'O insumo não pode estar vazio' }) + @IsNumber({}, { message: 'O insumo inserido não é válido' }) + idVariante?: number; + - unidade?: string; } diff --git a/src/modules/cotacoes/entities/cotacao.entity.ts b/src/modules/cotacoes/entities/cotacao.entity.ts index 4d28143..64bd072 100644 --- a/src/modules/cotacoes/entities/cotacao.entity.ts +++ b/src/modules/cotacoes/entities/cotacao.entity.ts @@ -1,10 +1,11 @@ export class Cotacao { id: number; data: Date; + obsoleta: boolean; valor: number; idFornecedor: number; - idInsumo: number; - unidade: string; + idVariante: number; + createdAt: Date; updatedAt: Date; } diff --git a/src/modules/fornecedores/dto/create-fornecedor.dto.ts b/src/modules/fornecedores/dto/create-fornecedor.dto.ts index 65bca1e..355c768 100644 --- a/src/modules/fornecedores/dto/create-fornecedor.dto.ts +++ b/src/modules/fornecedores/dto/create-fornecedor.dto.ts @@ -1,107 +1,159 @@ import { ApiProperty } from '@nestjs/swagger'; import { contaTipo } from '@prisma/client'; +import { IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsOptional, IsString, Matches, ValidateIf } from 'class-validator'; export class CreateFornecedorDto { @ApiProperty({ - description: 'O email serve pare descrever o email do fornecedor', + description: 'O email serve para descrever o email do fornecedor', example: 'email@gmail.com', }) + @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) + @IsEmail({}, { message: 'O e-mail inserido não é válido' }) email: string; - + @ApiProperty({ - description: 'O telefone serve para descrever o numero de telefone do fornecedor', + description: + 'O telefone serve para descrever o numero de telefone do fornecedor', example: '1734112736', }) + @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) + @IsNumberString({}, { message: 'O telefone inserido não é válido' }) telefone: string; - + @ApiProperty({ description: 'O tipo serve para diferenciar entre pessoa fisica e juridica', example: 'Fisica', }) + @IsNotEmpty({ message: 'O tipo da conta não pode estar vazio' }) + @IsEnum(contaTipo, { + message: 'O tipo da conta não condiz com as opções disponíveis', + }) contaTipo: contaTipo; - + + @ApiProperty({ + description: + 'O nome serve para identificar o fornecedor, caso seja pessoa fisica', + example: 'João Pedro', + }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsString({ message: 'O nome inserido não é válido' }) + nome?: string; + @ApiProperty({ - description: 'O pais serve para identificar a região onde o fornecedor se encontra', + description: + 'O CPF serve para identificar o fornecedor, caso seja pessoa fisica', + example: '02370334029', + }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O CPF inserido não é válido' }) + cpf?: string; + + @ApiProperty({ + description: + 'O RG serve para identificar o fornecedor, caso seja pessoa fisica', + example: '114421225', + }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O RG inserido não é válido' }) + rg?: string; + + @ApiProperty({ + description: + 'O nome fantasia serve para identificar o fornecedor, caso seja pessoa juridica', + example: 'ZawnTech', + }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'O nome fantasia inserido não é válido' }) + nomeFantasia?: string; + + @ApiProperty({ + description: + 'A razão social serve para identificar o fornecedor, caso seja pessoa juridica', + example: 'Industria mecanica modelo Ltda.', + }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'A razão social inserida não é válida' }) + razaoSocial?: string; + + @ApiProperty({ + description: + 'O CNPJ serve para identificar o fornecedor, caso seja pessoa juridica', + example: '31895255000193', + }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsNumberString({}, { message: 'O CNPJ inserido não é válido' }) + cnpj?: string; + + @ApiProperty({ + description: + 'O pais serve para identificar a região onde o fornecedor se encontra', example: 'Brasil', }) + @IsOptional() + @IsString({ message: 'O país inserido não é válido' }) pais?: string; - + @ApiProperty({ - description: 'O CEP serve para identificar a região onde o fornecedor se encontra', + description: + 'O CEP serve para identificar a região onde o fornecedor se encontra', example: '69918170', }) + @IsOptional() + @IsNumberString({}, { message: 'O CEP inserido não é válido' }) cep?: string; - + @ApiProperty({ - description: 'O estado serve para identificar a região onde o fornecedor se encontra', + description: + 'O estado serve para identificar a região onde o fornecedor se encontra', example: 'SP', }) + @IsOptional() + @IsString({ message: 'O estado inserido não é válido' }) estado?: string; - + @ApiProperty({ - description: 'A cidade serve para identificar a região onde o fornecedor se encontra', + description: + 'A cidade serve para identificar a região onde o fornecedor se encontra', example: 'Sorocaba', }) + @IsOptional() + @IsString({ message: 'A cidade inserida não é válida' }) cidade?: string; - + @ApiProperty({ - description: 'O bairro serve para identificar o local onde o fornecedor se encontra', + description: + 'O bairro serve para identificar o local onde o fornecedor se encontra', example: 'Vila Barão', }) + @IsOptional() + @IsString({ message: 'O bairro inserido não é válido' }) bairro?: string; - + @ApiProperty({ - description: 'A rua serve para identificar o local onde o fornecedor se encontra', + description: + 'A rua serve para identificar o local onde o fornecedor se encontra', example: 'Rua Manuel Lourenço Rodrigues', }) + @IsOptional() + @IsString({ message: 'A rua inserida não é válida' }) rua?: string; - + @ApiProperty({ - description: 'O numero serve para identificar o local onde o fornecedor se encontra', + description: + 'O numero serve para identificar o local onde o fornecedor se encontra', example: '44', }) + @IsOptional() + @IsNumberString({}, { message: 'O numero inserido não é válido' }) numero?: string; - + @ApiProperty({ - description: 'O complemento serve para dar informações adicionais para identificar o local onde o fornecedor se encontra', + description: + 'O complemento serve para dar informações adicionais para identificar o local onde o fornecedor se encontra', example: 'apt. 42', }) + @IsOptional() + @IsString({ message: 'O complemento inserido não é válido' }) complemento?: string; - - @ApiProperty({ - description: 'O nome serve para identificar o fornecedor, caso seja pessoa fisica', - example: 'João Pedro', - }) - nome?: string; - - @ApiProperty({ - description: 'O CPF serve para identificar o fornecedor, caso seja pessoa fisica', - example: '02370334029', - }) - cpf?: string; - - @ApiProperty({ - description: 'O RG serve para identificar o fornecedor, caso seja pessoa fisica', - example: '114421225', - }) - rg?: string; - - @ApiProperty({ - description: 'O nome fantasia serve para identificar o fornecedor, caso seja pessoa juridica', - example: 'ZawnTech', - }) - nomeFantasia?: string; - - @ApiProperty({ - description: 'A razão social serve para identificar o fornecedor, caso seja pessoa juridica', - example: 'Industria mecanica modelo Ltda.', - }) - razaoSocial?: string; - - @ApiProperty({ - description: 'O CNPJ serve para identificar o fornecedor, caso seja pessoa juridica', - example: '31895255000193', - }) - cnpj?: string; } diff --git a/src/modules/fornecedores/dto/update-fornecedor.dto.ts b/src/modules/fornecedores/dto/update-fornecedor.dto.ts index 971fd93..2adc122 100644 --- a/src/modules/fornecedores/dto/update-fornecedor.dto.ts +++ b/src/modules/fornecedores/dto/update-fornecedor.dto.ts @@ -2,108 +2,159 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateFornecedorDto } from './create-fornecedor.dto'; import { contaTipo } from '@prisma/client'; import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsOptional, IsString, Matches, ValidateIf } from 'class-validator'; export class UpdateFornecedorDto extends PartialType(CreateFornecedorDto) { @ApiProperty({ - description: 'O email serve pare descrever o email do fornecedor', + description: 'O email serve para descrever o email do fornecedor', example: 'email@gmail.com', }) + @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) + @IsEmail({}, { message: 'O e-mail inserido não é válido' }) email?: string; - + @ApiProperty({ - description: 'O telefone serve para descrever o numero de telefone do fornecedor', + description: + 'O telefone serve para descrever o numero de telefone do fornecedor', example: '1734112736', }) + @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) + @IsNumberString({}, { message: 'O telefone inserido não é válido' }) telefone?: string; - + @ApiProperty({ description: 'O tipo serve para diferenciar entre pessoa fisica e juridica', example: 'Fisica', }) + @IsNotEmpty({ message: 'O tipo da conta não pode estar vazio' }) + @IsEnum(contaTipo, { + message: 'O tipo da conta não condiz com as opções disponíveis', + }) contaTipo?: contaTipo; - + + @ApiProperty({ + description: + 'O nome serve para identificar o fornecedor, caso seja pessoa fisica', + example: 'João Pedro', + }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsString({ message: 'O nome inserido não é válido' }) + nome?: string; + @ApiProperty({ - description: 'O pais serve para identificar a região onde o fornecedor se encontra', + description: + 'O CPF serve para identificar o fornecedor, caso seja pessoa fisica', + example: '02370334029', + }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O CPF inserido não é válido' }) + cpf?: string; + + @ApiProperty({ + description: + 'O RG serve para identificar o fornecedor, caso seja pessoa fisica', + example: '114421225', + }) + @ValidateIf(o => o.contaTipo == "Fisica" ) + @IsNumberString({}, { message: 'O RG inserido não é válido' }) + rg?: string; + + @ApiProperty({ + description: + 'O nome fantasia serve para identificar o fornecedor, caso seja pessoa juridica', + example: 'ZawnTech', + }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'O nome fantasia inserido não é válido' }) + nomeFantasia?: string; + + @ApiProperty({ + description: + 'A razão social serve para identificar o fornecedor, caso seja pessoa juridica', + example: 'Industria mecanica modelo Ltda.', + }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsString({ message: 'A razão social inserida não é válida' }) + razaoSocial?: string; + + @ApiProperty({ + description: + 'O CNPJ serve para identificar o fornecedor, caso seja pessoa juridica', + example: '31895255000193', + }) + @ValidateIf(o => o.contaTipo == "Juridica" ) + @IsNumberString({}, { message: 'O CNPJ inserido não é válido' }) + cnpj?: string; + + @ApiProperty({ + description: + 'O pais serve para identificar a região onde o fornecedor se encontra', example: 'Brasil', }) + @IsOptional() + @IsString({ message: 'O país inserido não é válido' }) pais?: string; - + @ApiProperty({ - description: 'O CEP serve para identificar a região onde o fornecedor se encontra', + description: + 'O CEP serve para identificar a região onde o fornecedor se encontra', example: '69918170', }) + @IsOptional() + @IsNumberString({}, { message: 'O CEP inserido não é válido' }) cep?: string; - + @ApiProperty({ - description: 'O estado serve para identificar a região onde o fornecedor se encontra', + description: + 'O estado serve para identificar a região onde o fornecedor se encontra', example: 'SP', }) + @IsOptional() + @IsString({ message: 'O estado inserido não é válido' }) estado?: string; - + @ApiProperty({ - description: 'A cidade serve para identificar a região onde o fornecedor se encontra', + description: + 'A cidade serve para identificar a região onde o fornecedor se encontra', example: 'Sorocaba', }) + @IsOptional() + @IsString({ message: 'A cidade inserida não é válida' }) cidade?: string; - + @ApiProperty({ - description: 'O bairro serve para identificar o local onde o fornecedor se encontra', + description: + 'O bairro serve para identificar o local onde o fornecedor se encontra', example: 'Vila Barão', }) + @IsOptional() + @IsString({ message: 'O bairro inserido não é válido' }) bairro?: string; - + @ApiProperty({ - description: 'A rua serve para identificar o local onde o fornecedor se encontra', + description: + 'A rua serve para identificar o local onde o fornecedor se encontra', example: 'Rua Manuel Lourenço Rodrigues', }) + @IsOptional() + @IsString({ message: 'A rua inserida não é válida' }) rua?: string; - + @ApiProperty({ - description: 'O numero serve para identificar o local onde o fornecedor se encontra', + description: + 'O numero serve para identificar o local onde o fornecedor se encontra', example: '44', }) + @IsOptional() + @IsNumberString({}, { message: 'O numero inserido não é válido' }) numero?: string; - + @ApiProperty({ - description: 'O complemento serve para dar informações adicionais para identificar o local onde o fornecedor se encontra', + description: + 'O complemento serve para dar informações adicionais para identificar o local onde o fornecedor se encontra', example: 'apt. 42', }) + @IsOptional() + @IsString({ message: 'O complemento inserido não é válido' }) complemento?: string; - - @ApiProperty({ - description: 'O nome serve para identificar o fornecedor, caso seja pessoa fisica', - example: 'João Pedro', - }) - nome?: string; - - @ApiProperty({ - description: 'O CPF serve para identificar o fornecedor, caso seja pessoa fisica', - example: '02370334029', - }) - cpf?: string; - - @ApiProperty({ - description: 'O RG serve para identificar o fornecedor, caso seja pessoa fisica', - example: '114421225', - }) - rg?: string; - - @ApiProperty({ - description: 'O nome fantasia serve para identificar o fornecedor, caso seja pessoa juridica', - example: 'ZawnTech', - }) - nomeFantasia?: string; - - @ApiProperty({ - description: 'A razão social serve para identificar o fornecedor, caso seja pessoa juridica', - example: 'Industria mecanica modelo Ltda.', - }) - razaoSocial?: string; - - @ApiProperty({ - description: 'O CNPJ serve para identificar o fornecedor, caso seja pessoa juridica', - example: '31895255000193', - }) - cnpj?: string; - } diff --git a/src/modules/fornecedores/entities/fornecedor.entity.ts b/src/modules/fornecedores/entities/fornecedor.entity.ts index 8db2b93..f1e92f7 100644 --- a/src/modules/fornecedores/entities/fornecedor.entity.ts +++ b/src/modules/fornecedores/entities/fornecedor.entity.ts @@ -5,6 +5,12 @@ export class Fornecedor { email: string; telefone: string; contaTipo: contaTipo; + nome?: string; + cpf?: string; + rg?: string; + nomeFantasia?: string; + razaoSocial?: string; + cnpj?: string; pais?: string; cep?: string; estado?: string; @@ -13,12 +19,6 @@ export class Fornecedor { rua?: string; numero?: string; complemento?: string; - nome?: string; - cpf?: string; - rg?: string; - nomeFantasia?: string; - razaoSocial?: string; - cnpj?: string; createdAt: Date; updatedAt: Date; } diff --git a/src/modules/fornecedores/fornecedores.controller.ts b/src/modules/fornecedores/fornecedores.controller.ts index be9c130..511d3ba 100644 --- a/src/modules/fornecedores/fornecedores.controller.ts +++ b/src/modules/fornecedores/fornecedores.controller.ts @@ -7,55 +7,63 @@ import { Param, Delete, Query, + UsePipes, + ValidationPipe, + Header, + Res, } from '@nestjs/common'; import { FornecedoresService } from './fornecedores.service'; import { CreateFornecedorDto } from './dto/create-fornecedor.dto'; import { UpdateFornecedorDto } from './dto/update-fornecedor.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from "express"; @ApiTags('fornecedores') @Controller('fornecedores') export class FornecedoresController { constructor(private readonly fornecedoresService: FornecedoresService) {} - @Get('paginate') -async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.fornecedoresService.countAllFornecedor(); - res.set('x-total-count', totalcount.toString()); - return await this.fornecedoresService.findAllWithPagination(page, perPage); -} @Get('count') - countAll() { - return this.fornecedoresService.countAllFornecedor(); + async countAll() { + return await this.fornecedoresService.countAllFornecedor(); } + @Post() - create(@Body() CreateFornecedoresDto: CreateFornecedorDto) { - return this.fornecedoresService.create(CreateFornecedoresDto); + async create(@Body() CreateFornecedoresDto: CreateFornecedorDto) { + return await this.fornecedoresService.create(CreateFornecedoresDto); } @Get() - findAll() { - return this.fornecedoresService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('page') page: number,@Query('perPage') perPage: number,@Query('nome_like') nome_like : string, @Res({ passthrough: true }) res) { + page = page||1; + perPage = perPage||await this.countAll(); + const fornecedores = await this.fornecedoresService.findAllWithPagination( + page, + Number(perPage), + nome_like + ); + const total = await this.fornecedoresService.countAllFornecedor(nome_like); + res.header('x-total-count',total); + return await fornecedores } @Get(':id') async findOne(@Param('id') id: string) { return await this.fornecedoresService.findOne(+id); } - + + @Patch(':id') - update( + async update( @Param('id') id: string, @Body() UpdateFornecedoresDto: UpdateFornecedorDto, ) { - return this.fornecedoresService.update(+id, UpdateFornecedoresDto); + return await this.fornecedoresService.update(+id, UpdateFornecedoresDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.fornecedoresService.remove(+id); + async remove(@Param('id') id: string) { + return await this.fornecedoresService.remove(+id); } } diff --git a/src/modules/fornecedores/fornecedores.module.ts b/src/modules/fornecedores/fornecedores.module.ts index 917d9ea..5e15dfd 100644 --- a/src/modules/fornecedores/fornecedores.module.ts +++ b/src/modules/fornecedores/fornecedores.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { FornecedoresService } from './fornecedores.service'; import { FornecedoresController } from './fornecedores.controller'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [FornecedoresController], - providers: [FornecedoresService, PrismaService], + providers: [FornecedoresService], }) export class FornecedoresModule {} diff --git a/src/modules/fornecedores/fornecedores.service.ts b/src/modules/fornecedores/fornecedores.service.ts index 5a143ee..d8ec208 100644 --- a/src/modules/fornecedores/fornecedores.service.ts +++ b/src/modules/fornecedores/fornecedores.service.ts @@ -1,43 +1,151 @@ import { Injectable } from '@nestjs/common'; import { CreateFornecedorDto } from './dto/create-fornecedor.dto'; import { UpdateFornecedorDto } from './dto/update-fornecedor.dto'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { Fornecedor } from './entities/fornecedor.entity'; @Injectable() export class FornecedoresService { constructor(private readonly prismaService: PrismaService) {} - - async findAllWithPagination(page: number, perPage: number) { + + async findAllWithPagination(page: number, perPage: number, nome_like? : string) { const skip = (page - 1) * perPage; - const fornecedores = await this.prismaService.fornecedor.findMany({ - skip, - take: perPage, - }); - return { fornecedores }; + let fornecedores = Fornecedor[""]; + if(nome_like){ + fornecedores = await this.prismaService.fornecedor.findMany({ + skip, + take: perPage, + where:{ + OR: [{ nome: { contains: nome_like, mode: 'insensitive' } }, + { email: { contains: nome_like , mode: 'insensitive'} }, + { rua: { contains: nome_like , mode: 'insensitive'} }, + { nomeFantasia: { contains: nome_like, mode: 'insensitive' } }, + { razaoSocial: { contains: nome_like , mode: 'insensitive'} },], + }, + }); + }else{ + fornecedores = await this.prismaService.fornecedor.findMany({ + skip, + take: perPage, + }); + } + + + return fornecedores ; } - async findOneByFornecedor(nome: string,email: string,telefone:string) { + async findOneByFornecedor(nome: string, email: string, telefone: string) { return await this.prismaService.fornecedor.findFirst({ - where: { nome,email,telefone }, + where: { nome, email, telefone }, }); } - async findManyByFornecedor(nome: string,email: string,telefone:string){ + async findManyByFornecedor(nome: string, email: string, telefone: string) { return await this.prismaService.fornecedor.findMany({ - where: {nome,email,telefone} - }) - } - async countAllFornecedor(){ - return await this.prismaService.fornecedor.count({ + where: { nome, email, telefone }, }); } + async countAllFornecedor(nome_like: string = '') { + return await this.prismaService.fornecedor.count({where:{ + OR: [{ nome: { contains: nome_like, mode: 'insensitive' } }, + { email: { contains: nome_like , mode: 'insensitive'} }, + { rua: { contains: nome_like , mode: 'insensitive'} }, + { nomeFantasia: { contains: nome_like, mode: 'insensitive' } }, + { razaoSocial: { contains: nome_like , mode: 'insensitive'} },], + },}); + } + + async findExistingFornecedor(id: number, termo: string) { + if (!termo === undefined) { + var emailExists = await this.prismaService.fornecedor.findUnique({ + where: { + email: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var cpfExists = await this.prismaService.fornecedor.findUnique({ + where: { + cpf: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var rgExists = await this.prismaService.fornecedor.findUnique({ + where: { + rg: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var cnpjExists = await this.prismaService.fornecedor.findUnique({ + where: { + cnpj: termo, + NOT: { + id: id, + }, + }, + }); + } + if (!termo === undefined) { + var razaoExists = await this.prismaService.fornecedor.findUnique({ + where: { + razaoSocial: termo, + NOT: { + id: id, + }, + }, + }); + } + if ( + !emailExists && + !cpfExists && + !rgExists && + !cnpjExists && + !razaoExists + ) { + return await 0; + } + return await 1; + } + async create(createFornecedoresDto: CreateFornecedorDto) { - const cliente = await this.findOneByFornecedor(createFornecedoresDto.nome,createFornecedoresDto.email,createFornecedoresDto.telefone); - if (!cliente) { + if ( + !(await this.findExistingFornecedor( + undefined, + createFornecedoresDto.email, + )) && + !(await this.findExistingFornecedor( + undefined, + createFornecedoresDto.cpf, + )) && + !(await this.findExistingFornecedor( + undefined, + createFornecedoresDto.rg, + )) && + !(await this.findExistingFornecedor( + undefined, + createFornecedoresDto.cnpj, + )) && + !(await this.findExistingFornecedor( + undefined, + createFornecedoresDto.razaoSocial, + )) + ) { return await this.prismaService.fornecedor.create({ data: createFornecedoresDto, }); } - return { data: { message: 'Cliente ja cadastrado' } }; + + return { data: { message: 'fornecedor com dados repetidos' } }; } async findAll() { @@ -45,17 +153,49 @@ export class FornecedoresService { } async findOne(id: number) { - return await this.prismaService.fornecedor.findFirst({where: {id}}) + return await this.prismaService.fornecedor.findFirst({ where: { id } }); } async update(id: number, updateFornecedoresDto: UpdateFornecedorDto) { - return await this.prismaService.fornecedor.update({ - where: {id}, - data: updateFornecedoresDto, - }) + const fornecedorExists = await this.findOne(id); + if (fornecedorExists) { + if ( + !(await this.findExistingFornecedor( + fornecedorExists.id, + updateFornecedoresDto.email, + )) && + !(await this.findExistingFornecedor( + fornecedorExists.id, + updateFornecedoresDto.cpf, + )) && + !(await this.findExistingFornecedor( + fornecedorExists.id, + updateFornecedoresDto.rg, + )) && + !(await this.findExistingFornecedor( + fornecedorExists.id, + updateFornecedoresDto.cnpj, + )) && + !(await this.findExistingFornecedor( + fornecedorExists.id, + updateFornecedoresDto.razaoSocial, + )) + ) { + return await this.prismaService.fornecedor.update({ + where: { id }, + data: updateFornecedoresDto, + }); + } + return { data: { message: 'fornecedor com dados repetidos' } }; + } + return { data: { message: 'fornecedor não existe' } }; } async remove(id: number) { - return await this.prismaService.fornecedor.delete({where: {id}}) + const fornecedorExists = await this.findOne(id); + if (fornecedorExists) { + return await this.prismaService.fornecedor.delete({ where: { id } }); + } + return { data: { message: 'fornecedor não existe' } }; } } diff --git a/src/modules/insumos-produtos-base/dto/create-insumo-produtos-base.dto.ts b/src/modules/insumos-produtos-base/dto/create-insumo-produtos-base.dto.ts index d6a9b3a..eb8a60b 100644 --- a/src/modules/insumos-produtos-base/dto/create-insumo-produtos-base.dto.ts +++ b/src/modules/insumos-produtos-base/dto/create-insumo-produtos-base.dto.ts @@ -1,4 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString, ValidateIf } from 'class-validator'; export class CreateInsumosProdutosBaseDto { @ApiProperty({ @@ -6,6 +7,8 @@ export class CreateInsumosProdutosBaseDto { 'A quantidade serve para descrever quantas unidades deste insumo serão necessárias para produzir o produto', example: '5', }) + @IsNotEmpty({ message: 'A quantidade não pode estar vazia' }) + @IsNumber({}, { message: 'A quantidade inserida não é válida' }) quantidade: number; @ApiProperty({ @@ -13,6 +16,8 @@ export class CreateInsumosProdutosBaseDto { 'O id do produto base serve para descrever a qual produto base que um determinado insumo pertence', example: '1', }) + @IsNotEmpty({ message: 'O produto base não pode estar vazio' }) + @IsNumber({}, { message: 'O produto base inserido não é válido' }) idProdutoBase: number; @ApiProperty({ @@ -20,5 +25,9 @@ export class CreateInsumosProdutosBaseDto { 'O id do insumo serve para descrever para qual insumo este insumo produto base aponta', example: '1', }) - idInsumo: number; + @IsNotEmpty({ message: 'O insumo não pode estar vazio' }) + @IsNumber({}, { message: 'O insumo inserido não é válido' }) + idVariante: number; + + } diff --git a/src/modules/insumos-produtos-base/dto/update-insumo-produtos-base.dto.ts b/src/modules/insumos-produtos-base/dto/update-insumo-produtos-base.dto.ts index aa0ce13..903ca4a 100644 --- a/src/modules/insumos-produtos-base/dto/update-insumo-produtos-base.dto.ts +++ b/src/modules/insumos-produtos-base/dto/update-insumo-produtos-base.dto.ts @@ -1,6 +1,7 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateInsumosProdutosBaseDto } from './create-insumo-produtos-base.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString, ValidateIf } from 'class-validator'; export class UpdateInsumosProdutosBaseDto extends PartialType( CreateInsumosProdutosBaseDto, @@ -10,6 +11,8 @@ export class UpdateInsumosProdutosBaseDto extends PartialType( 'A quantidade serve para descrever quantas unidades deste insumo serão necessárias para produzir o produto', example: '5', }) + @IsNotEmpty({message: 'A quantidade não pode estar vazia'}) + @IsNumber({},{message: 'A quantidade inserida não é válida'}) quantidade?: number; @ApiProperty({ @@ -17,6 +20,8 @@ export class UpdateInsumosProdutosBaseDto extends PartialType( 'O id do produto base serve para descrever a qual produto base que um determinado insumo pertence', example: '1', }) + @IsNotEmpty({message: 'O produto base não pode estar vazio'}) + @IsNumber({},{message: 'O produto base inserido não é válido'}) idProdutoBase?: number; @ApiProperty({ @@ -24,5 +29,9 @@ export class UpdateInsumosProdutosBaseDto extends PartialType( 'O id do insumo serve para descrever para qual insumo este insumo produto base aponta', example: '1', }) - idInsumo?: number; + @IsNotEmpty({message: 'Selecione um insumo'}) + @IsNumber({},{message: 'O insumo inserido não é válido'}) + idVariante?: number; + + } diff --git a/src/modules/insumos-produtos-base/entities/insumo-produtos-base.entity.ts b/src/modules/insumos-produtos-base/entities/insumo-produtos-base.entity.ts index aa49ef4..c3f1fd4 100644 --- a/src/modules/insumos-produtos-base/entities/insumo-produtos-base.entity.ts +++ b/src/modules/insumos-produtos-base/entities/insumo-produtos-base.entity.ts @@ -1,14 +1,14 @@ import { ProdutoBase } from '@prisma/client'; -import { Insumo } from 'src/modules/insumos/entities/insumo.entity'; +import { Variante } from 'src/modules/variantes/entities/variante.entity'; export class InsumoProdutosBase { id: number; - quantidade?: number; + quantidade: number; idProdutoBase: number; - idInsumo: number; - unidade: string; + idVariante: number; + produtoBase: ProdutoBase; - insumo: Insumo; + variante: Variante; createdAt: Date; updatedAt: Date; } diff --git a/src/modules/insumos-produtos-base/insumos-produtos-base.controller.ts b/src/modules/insumos-produtos-base/insumos-produtos-base.controller.ts index bb219c1..24aafbc 100644 --- a/src/modules/insumos-produtos-base/insumos-produtos-base.controller.ts +++ b/src/modules/insumos-produtos-base/insumos-produtos-base.controller.ts @@ -1,55 +1,62 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; +import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UsePipes, ValidationPipe, Header, Res } from '@nestjs/common'; import { InsumosProdutosBaseService } from './insumos-produtos-base.service'; import { CreateInsumosProdutosBaseDto } from './dto/create-insumo-produtos-base.dto'; import { UpdateInsumosProdutosBaseDto } from './dto/update-insumo-produtos-base.dto'; import { ApiTags } from '@nestjs/swagger'; -import {response as res} from 'express'; @ApiTags('insumos-produtos-base') @Controller('insumos-produtos-base') export class InsumosProdutosBaseController { constructor(private readonly insumosProdutosBaseService: InsumosProdutosBaseService) {} - @Get('paginate') -async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.insumosProdutosBaseService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.insumosProdutosBaseService.findAllWithPagination(page, perPage); -} @Get('count') - countAll() { - return this.insumosProdutosBaseService.countAll(); + async countAll(idProdutobase: number) { + return await this.insumosProdutosBaseService.countAll(idProdutobase); } @Get('insumoProd/:id') - findProdutoOrc(@Param('id') id: number) + async findProdutoOrc(@Param('id') id: number) { - return this.insumosProdutosBaseService.findInsumoProdBase(+id); + return await this.insumosProdutosBaseService.findInsumoProdBase(+id); } + @Post() - create(@Body() createInsumosProdutosBaseDto: CreateInsumosProdutosBaseDto) { - return this.insumosProdutosBaseService.create(createInsumosProdutosBaseDto); + async create(@Body() createInsumosProdutosBaseDto: CreateInsumosProdutosBaseDto) { + return await this.insumosProdutosBaseService.create(createInsumosProdutosBaseDto); } - @Get() - findAll() { - return this.insumosProdutosBaseService.findAll(); + @Get('insumo/:id') + async findOne(@Param('id') id: string) { + return await this.insumosProdutosBaseService.findOne(+id); } - @Get(':id') - findOne(@Param('id') id: string) { - return this.insumosProdutosBaseService.findOne(+id); + @Get(":id") + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Param('id') id: string,@Query('page') page: number,@Query('perPage') perPage: number,@Query('titulo_like') busca : string, @Res({ passthrough: true }) res) { + + page = page||1; + perPage = perPage||await this.countAll(+id); + const insumosBase = await this.insumosProdutosBaseService.findAllWithPagination( + +id, + page, + Number(perPage), + busca, + + ); + const total = await this.insumosProdutosBaseService.countAll(+id,busca); + res.header('x-total-count',total); + return await insumosBase } + @Patch(':id') - update(@Param('id') id: string, @Body() updateInsumosProdutosBaseDto: UpdateInsumosProdutosBaseDto) { - return this.insumosProdutosBaseService.update(+id, updateInsumosProdutosBaseDto); + async update(@Param('id') id: string, @Body() updateInsumosProdutosBaseDto: UpdateInsumosProdutosBaseDto) { + return await this.insumosProdutosBaseService.update(+id, updateInsumosProdutosBaseDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.insumosProdutosBaseService.remove(+id); + async remove(@Param('id') id: string) { + return await this.insumosProdutosBaseService.remove(+id); } } diff --git a/src/modules/insumos-produtos-base/insumos-produtos-base.module.ts b/src/modules/insumos-produtos-base/insumos-produtos-base.module.ts index 2e03964..ee715fa 100644 --- a/src/modules/insumos-produtos-base/insumos-produtos-base.module.ts +++ b/src/modules/insumos-produtos-base/insumos-produtos-base.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { InsumosProdutosBaseService } from './insumos-produtos-base.service'; import { InsumosProdutosBaseController } from './insumos-produtos-base.controller'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [InsumosProdutosBaseController], - providers: [InsumosProdutosBaseService,PrismaService], + providers: [InsumosProdutosBaseService], + exports: [InsumosProdutosBaseService], }) export class InsumosProdutosBaseModule {} diff --git a/src/modules/insumos-produtos-base/insumos-produtos-base.service.ts b/src/modules/insumos-produtos-base/insumos-produtos-base.service.ts index dd6bee5..7fe6951 100644 --- a/src/modules/insumos-produtos-base/insumos-produtos-base.service.ts +++ b/src/modules/insumos-produtos-base/insumos-produtos-base.service.ts @@ -1,38 +1,72 @@ import { Injectable } from '@nestjs/common'; import { CreateInsumosProdutosBaseDto } from './dto/create-insumo-produtos-base.dto'; import { UpdateInsumosProdutosBaseDto } from './dto/update-insumo-produtos-base.dto'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { InsumoProdutosBase } from './entities/insumo-produtos-base.entity'; @Injectable() export class InsumosProdutosBaseService { constructor(private readonly prismaService: PrismaService) {} - - async findAllWithPagination(page: number, perPage: number) { + + async findAllWithPagination( + idProdutobase: number, + page: number, + perPage: number, + busca: string, + ) { const skip = (page - 1) * perPage; - const insumosProdutosBase = await this.prismaService.insumoProdutoBase.findMany({ - skip, - take: perPage, - }); + let insumosProdutoBase = InsumoProdutosBase['']; - return { insumosProdutosBase }; + insumosProdutoBase = await this.prismaService.insumoProdutoBase.findMany({ + skip, + take: perPage, + where: { + idProdutoBase: idProdutobase, + OR: [ + { variantes: { insumo: { titulo: { contains: busca , mode: 'insensitive'} } } }, + + + ], + }, + }); + return insumosProdutoBase; } async create(createInsumosProdutosBaseDto: CreateInsumosProdutosBaseDto) { - return await this.prismaService.insumoProdutoBase.create({ - data: createInsumosProdutosBaseDto, + const insumoExists = await this.prismaService.variante.findFirst({ + where: { id: createInsumosProdutosBaseDto.idVariante }, }); + if (insumoExists) { + const produtoBaseExists = await this.prismaService.produtoBase.findFirst({ + where: { id: createInsumosProdutosBaseDto.idProdutoBase }, + }); + if (produtoBaseExists) { + return await this.prismaService.insumoProdutoBase.create({ + data: createInsumosProdutosBaseDto, + }); + } + return { data: { message: 'Produto base não existe' } }; + } + return { data: { message: 'Insumo não existe' } }; } - async countAll(){ - return await this.prismaService.insumoProdutoBase.count(); + async countAll(idProdutobase: number,busca : string = '') { + return await this.prismaService.insumoProdutoBase.count({ + where: { + idProdutoBase: idProdutobase, + OR: [ + { variantes: { insumo: { titulo: { contains: busca , mode: 'insensitive'} } } }, + + + ], + }, + }); } async findInsumoProdBase(id: number) { return await this.prismaService.insumoProdutoBase.findMany({ where: { - OR: [ - {idProdutoBase: {equals: id}} - ], + OR: [{ idProdutoBase: { equals: id } }], }, }); } @@ -42,17 +76,40 @@ export class InsumosProdutosBaseService { } async findOne(id: number) { - return await this.prismaService.insumoProdutoBase.findFirst({ where: { id } }); + return await this.prismaService.insumoProdutoBase.findFirst({ + where: { id }, + }); } - async update(id: number, updateInsumosProdutosBaseDto: UpdateInsumosProdutosBaseDto) { - return await this.prismaService.insumoProdutoBase.update({ - where: { id }, - data: updateInsumosProdutosBaseDto, - }) + async update( + id: number, + updateInsumosProdutosBaseDto: UpdateInsumosProdutosBaseDto, + ) { + const insumoExists = await this.prismaService.variante.findFirst({ + where: { id: updateInsumosProdutosBaseDto.idVariante }, + }); + if (insumoExists) { + const produtoBaseExists = await this.prismaService.produtoBase.findFirst({ + where: { id: updateInsumosProdutosBaseDto.idProdutoBase }, + }); + if (produtoBaseExists) { + return await this.prismaService.insumoProdutoBase.update({ + where: { id }, + data: updateInsumosProdutosBaseDto, + }); + } + return { data: { message: 'Produto base não existe' } }; + } + return { data: { message: 'Insumo não existe' } }; } async remove(id: number) { - return await this.prismaService.insumoProdutoBase.delete({ where: { id } }) + const insumoBaseExists = await this.findOne(id); + if (insumoBaseExists) { + return await this.prismaService.insumoProdutoBase.delete({ + where: { id }, + }); + } + return { data: { message: 'Insumo Base não existe' } }; } } diff --git a/src/modules/insumos/dto/create-insumo.dto.ts b/src/modules/insumos/dto/create-insumo.dto.ts index a3b1a65..d856b14 100644 --- a/src/modules/insumos/dto/create-insumo.dto.ts +++ b/src/modules/insumos/dto/create-insumo.dto.ts @@ -1,31 +1,21 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, Matches } from 'class-validator'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, Matches, ValidateIf } from 'class-validator'; export class CreateInsumoDto { @ApiProperty({ description: 'O titulo serve para pesquisar insumos', example: 'Tubo de metalon', }) - @IsNotEmpty({ message: 'O insumo deve ter um titulo' }) - @Matches(/^[a-zA-Z -]*$/, { message: 'O nome do insumo só pode ter letras' }) + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) titulo: string; - - @ApiProperty({ - description: 'A descrição serve para detalhar o insumo', - example: '20 x 30 x 6.000 mm', - }) - descricao?: string; - + @ApiProperty({ - description: 'A unidade de medida serve para destacar a forma que o insumo é medido', - example: 'mm', - }) - unidadeMedida?: string; - - @ApiProperty({ - description: 'O Id da categoria serve para conectar o insumo a uma categoria', + description: + 'O Id da categoria serve para conectar o insumo a uma categoria', example: '1', }) - idCategoria?: number; - + @ValidateIf((object, value) => value !== undefined) + @IsNumber({}, { message: 'A categoria inserida não é válida' }) + idCategoria: number; } diff --git a/src/modules/insumos/dto/update-insumo.dto.ts b/src/modules/insumos/dto/update-insumo.dto.ts index 2d8cff5..85d7452 100644 --- a/src/modules/insumos/dto/update-insumo.dto.ts +++ b/src/modules/insumos/dto/update-insumo.dto.ts @@ -1,29 +1,23 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateInsumoDto } from './create-insumo.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, ValidateIf } from 'class-validator'; export class UpdateInsumoDto extends PartialType(CreateInsumoDto) { @ApiProperty({ description: 'O titulo serve para pesquisar insumos', example: 'Tubo de metalon', }) + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) titulo?: string; @ApiProperty({ - description: 'A descrição serve para detalhar o insumo', - example: '20 x 30 x 6.000 mm', - }) - descricao?: string; - - @ApiProperty({ - description: 'A unidade de medida serve para destacar a forma que o insumo é medido', - example: 'mm', - }) - unidadeMedida?: string; - - @ApiProperty({ - description: 'O Id da categoria serve para conectar o insumo a uma categoria', + description: + 'O Id da categoria serve para conectar o insumo a uma categoria', example: '1', }) - idCategoria?: number; + @IsNotEmpty({ message: 'Selecione uma categoria' }) + @IsNumber({}, { message: 'A categoria inserida não é válida' }) + idCategoria: number; } diff --git a/src/modules/insumos/entities/insumo.entity.ts b/src/modules/insumos/entities/insumo.entity.ts index 3f40fc2..1dd549e 100644 --- a/src/modules/insumos/entities/insumo.entity.ts +++ b/src/modules/insumos/entities/insumo.entity.ts @@ -1,12 +1,12 @@ +import { Variante } from 'src/modules/variantes/entities/variante.entity'; import { Categoria } from '../../categorias/entities/categoria.entity'; export class Insumo { id: number; titulo: string; - descricao?: string; - unidadeMedida?: string; - idCategoria?: number; + idCategoria: number; categoria?: Categoria; + variante?: Variante; createdAt: Date; updatedAt: Date; } diff --git a/src/modules/insumos/insumos.controller.ts b/src/modules/insumos/insumos.controller.ts index 6b5dc19..bcb7a18 100644 --- a/src/modules/insumos/insumos.controller.ts +++ b/src/modules/insumos/insumos.controller.ts @@ -9,40 +9,43 @@ import { UsePipes, ValidationPipe, Query, + Res, + Header, } from '@nestjs/common'; import { InsumosService } from './insumos.service'; import { CreateInsumoDto } from './dto/create-insumo.dto'; import { UpdateInsumoDto } from './dto/update-insumo.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from "express"; @ApiTags('insumos') @Controller('insumos') export class InsumosController { constructor(private readonly insumosService: InsumosService) {} - @Get('paginate') -async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.insumosService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.insumosService.findAllWithPagination(page, perPage); -} - @Get('count') - countAll() { - return this.insumosService.countAll(); + async countAll() { + return await this.insumosService.countAll(); } - @UsePipes(ValidationPipe) + @Post() - create(@Body() createInsumoDto: CreateInsumoDto) { - return this.insumosService.create(createInsumoDto); + async create(@Body() createInsumoDto: CreateInsumoDto) { + return await this.insumosService.create(createInsumoDto); } @Get() - findAll() { - return this.insumosService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('page') page: number,@Query('perPage') perPage: number,@Query('titulo_like') titulo_like : string, @Res({ passthrough: true }) res) { + page = page||1; + perPage = perPage||await this.insumosService.countAll(); + const cotacoes = await this.insumosService.findAllWithPagination( + page, + Number(perPage), + titulo_like + ); + const total = await this.insumosService.countAll(titulo_like); + res.header('x-total-count',total); + return await cotacoes } @Get(':id') @@ -50,13 +53,14 @@ async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPa return await this.insumosService.findOne(+id); } + @Patch(':id') - update(@Param('id') id: string, @Body() updateInsumoDto: UpdateInsumoDto) { - return this.insumosService.update(+id, updateInsumoDto); + async update(@Param('id') id: string, @Body() updateInsumoDto: UpdateInsumoDto) { + return await this.insumosService.update(+id, updateInsumoDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.insumosService.remove(+id); + async remove(@Param('id') id: string) { + return await this.insumosService.remove(+id); } } diff --git a/src/modules/insumos/insumos.module.ts b/src/modules/insumos/insumos.module.ts index 8b47f18..f8d1999 100644 --- a/src/modules/insumos/insumos.module.ts +++ b/src/modules/insumos/insumos.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { InsumosService } from './insumos.service'; import { InsumosController } from './insumos.controller'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [InsumosController], - providers: [InsumosService, PrismaService], + providers: [InsumosService], }) export class InsumosModule {} diff --git a/src/modules/insumos/insumos.service.ts b/src/modules/insumos/insumos.service.ts index 6ea433e..f73ee21 100644 --- a/src/modules/insumos/insumos.service.ts +++ b/src/modules/insumos/insumos.service.ts @@ -1,20 +1,40 @@ import { Injectable } from '@nestjs/common'; import { CreateInsumoDto } from './dto/create-insumo.dto'; import { UpdateInsumoDto } from './dto/update-insumo.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { Insumo } from './entities/insumo.entity'; @Injectable() export class InsumosService { constructor(private readonly prismaService: PrismaService) {} - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination( + page: number, + perPage: number, + titulo_like: string, + ) { const skip = (page - 1) * perPage; - const insumos = await this.prismaService.insumo.findMany({ - skip, - take: perPage, - }); + let insumos = Insumo['']; + if (titulo_like) { + insumos = await this.prismaService.insumo.findMany({ + skip, + take: perPage, + where: { + OR: [ + { titulo: { contains: titulo_like, mode: 'insensitive' } }, + + { categoria: { titulo: { contains: titulo_like, mode: 'insensitive' } } }, + ], + }, + }); + } else { + insumos = await this.prismaService.insumo.findMany({ + skip, + take: perPage, + }); + } - return { insumos}; + return insumos; } async findOneByTitle(titulo: string) { @@ -24,11 +44,22 @@ export class InsumosService { } async create(createInsumoDto: CreateInsumoDto) { - const insumo = await this.findOneByTitle(createInsumoDto.titulo); - if (!insumo) { - return await this.prismaService.insumo.create({ - data: createInsumoDto, - }); + const insumoRepetido = await this.findOneByTitle(createInsumoDto.titulo); + if (!insumoRepetido) { + if (createInsumoDto.idCategoria !== undefined) { + var categoriaExists = await this.prismaService.categoria.findFirst({ + where: { id: createInsumoDto.idCategoria }, + }); + } + if ( + (createInsumoDto.idCategoria && categoriaExists) || + (!createInsumoDto.idCategoria && !categoriaExists) + ) { + return await this.prismaService.insumo.create({ + data: createInsumoDto, + }); + } + return { data: { message: 'Categoria não existe' } }; } return { data: { message: 'Titulo ja cadastrado' } }; } @@ -43,18 +74,58 @@ export class InsumosService { }); } - async countAll() { - return await this.prismaService.insumo.count(); + async countAll(titulo_like: string = '') { + return await this.prismaService.insumo.count({where: { + OR: [ + { titulo: { contains: titulo_like, mode: 'insensitive' } }, + + { categoria: { titulo: { contains: titulo_like, mode: 'insensitive' } } }, + ], + },}); } async update(id: number, updateInsumoDto: UpdateInsumoDto) { - return await this.prismaService.insumo.update({ - where: { id }, - data: updateInsumoDto, - }); + const insumoRepetido = await this.findOneByTitle(updateInsumoDto.titulo); + if (!insumoRepetido) { + if (updateInsumoDto.idCategoria !== undefined) { + var categoriaExists = await this.prismaService.categoria.findFirst({ + where: { id: updateInsumoDto.idCategoria }, + }); + } + if ( + (updateInsumoDto.idCategoria && categoriaExists) || + (!updateInsumoDto.idCategoria && !categoriaExists) + ) { + return await this.prismaService.insumo.update({ + where: { id }, + data: updateInsumoDto, + }); + } + return { data: { message: 'Categoria não existe' } }; + } + return { data: { message: 'Titulo ja cadastrado' } }; } async remove(id: number) { - return await this.prismaService.insumo.delete({ where: { id } }); + const insumoExists = await this.findOne(id); + if (insumoExists) { + const insumoProds = await this.prismaService.listaInsumo.findFirst({ + where: { idVariante: id }, + }); + const insumoProdsBase = + await this.prismaService.insumoProdutoBase.findFirst({ + where: { idVariante: id }, + }); + const insumoCota = await this.prismaService.cotacao.findFirst({ + where: { idVariante: id }, + }); + if (!insumoProds && !insumoProdsBase && !insumoCota) { + return await this.prismaService.insumo.delete({ where: { id } }); + } + return { + data: { message: 'Insumo está sendo utilizado em outro local' }, + }; + } + return { data: { message: 'Insumo não existe' } }; } } diff --git a/src/modules/lista-insumos/dto/create-lista-insumo.dto.ts b/src/modules/lista-insumos/dto/create-lista-insumo.dto.ts index 504fb4f..b2366dc 100644 --- a/src/modules/lista-insumos/dto/create-lista-insumo.dto.ts +++ b/src/modules/lista-insumos/dto/create-lista-insumo.dto.ts @@ -1,5 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty } from 'class-validator'; +import { + IsNotEmpty, + IsNumber, + IsNumberString, + IsOptional, + IsString, + ValidateIf, +} from 'class-validator'; export class CreateListaInsumoDto { @ApiProperty({ @@ -7,7 +14,8 @@ export class CreateListaInsumoDto { 'A quantidade serve para descrever quantas unidades deste insumo serão necessárias para produzir o produto', example: '5', }) - @IsNotEmpty({ message: 'O insumo da lista deve ter uma quantidade' }) + @IsNotEmpty({ message: 'A quantidade não pode estar vazia' }) + @IsNumber({}, { message: 'A quantidade inserida não é válida' }) quantidade: number; @ApiProperty({ @@ -15,6 +23,8 @@ export class CreateListaInsumoDto { 'O id do produto serve para descrever a qual produto que um determinado insumo pertence', example: '1', }) + @IsNotEmpty({ message: 'O produto não pode estar vazio' }) + @IsNumber({}, { message: 'O produto inserido não é válido' }) idProduto: number; @ApiProperty({ @@ -22,14 +32,27 @@ export class CreateListaInsumoDto { 'O id do insumo serve para descrever para qual insumo este lista insumo aponta', example: '1', }) - idInsumo: number; + @IsNotEmpty({ message: 'O insumo não pode estar vazio' }) + @IsNumber({}, { message: 'O insumo inserido não é válido' }) + idVariante: number; @ApiProperty({ description: 'O id da cotação serve para descrever qual a cotação que determinará o custo do insumo', example: '5', }) + @IsOptional() + @IsNumber({}, { message: 'A cotação inserida não é válida' }) idCotacao?: number; - unidade?: string; + + + @ApiProperty({ + description: + 'O valor unitario serve para descrever qual é o valor do insumo', + example: '100,00', + }) + @IsOptional() + @IsNumberString({}, { message: 'O valor unitário inserido não é válido' }) + valorUnitario?: number; } diff --git a/src/modules/lista-insumos/dto/update-lista-insumo.dto.ts b/src/modules/lista-insumos/dto/update-lista-insumo.dto.ts index f99cd59..47edd86 100644 --- a/src/modules/lista-insumos/dto/update-lista-insumo.dto.ts +++ b/src/modules/lista-insumos/dto/update-lista-insumo.dto.ts @@ -1,7 +1,7 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateListaInsumoDto } from './create-lista-insumo.dto'; import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty, IsNumber, IsNumberString, IsOptional, IsString, ValidateIf } from 'class-validator'; export class UpdateListaInsumoDto extends PartialType(CreateListaInsumoDto) { @ApiProperty({ @@ -9,29 +9,45 @@ export class UpdateListaInsumoDto extends PartialType(CreateListaInsumoDto) { 'A quantidade serve para descrever quantas unidades deste insumo serão necessárias para produzir o produto', example: '5', }) - quantidade?: number; + @IsNotEmpty({ message: 'A quantidade não pode estar vazia' }) + @IsNumber({}, { message: 'A quantidade inserida não é válida' }) + quantidade: number; @ApiProperty({ description: 'O id do produto serve para descrever a qual produto que um determinado insumo pertence', example: '1', }) - idProduto?: number; + @IsNotEmpty({ message: 'O produto não pode estar vazio' }) + @IsNumber({}, { message: 'O produto inserido não é válido' }) + idProduto: number; @ApiProperty({ description: 'O id do insumo serve para descrever para qual insumo este lista insumo aponta', example: '1', }) - idInsumo?: number; + @IsNotEmpty({ message: 'O insumo não pode estar vazio' }) + @IsNumber({}, { message: 'O insumo inserido não é válido' }) + idVariante: number; @ApiProperty({ description: 'O id da cotação serve para descrever qual a cotação que determinará o custo do insumo', example: '5', }) + @IsOptional() + @IsNumber({}, { message: 'A cotação inserida não é válida' }) idCotacao?: number; - unidade?:string; + + @ApiProperty({ + description: + 'O valor unitario serve para descrever qual é o valor do insumo', + example: '100,00', + }) + @IsOptional() + @IsNumberString({},{message: 'O valor unitário inserido não é válido'}) + valorUnitario?: number; } diff --git a/src/modules/lista-insumos/entities/lista-insumo.entity.ts b/src/modules/lista-insumos/entities/lista-insumo.entity.ts index f0dd313..85cb068 100644 --- a/src/modules/lista-insumos/entities/lista-insumo.entity.ts +++ b/src/modules/lista-insumos/entities/lista-insumo.entity.ts @@ -1,16 +1,18 @@ import { Cotacao } from 'src/modules/cotacoes/entities/cotacao.entity'; import { Insumo } from 'src/modules/insumos/entities/insumo.entity'; import { Produto } from 'src/modules/produtos/entities/produto.entity'; +import { Variante } from 'src/modules/variantes/entities/variante.entity'; export class ListaInsumo { id: number; quantidade: number; idProduto: number; - idInsumo: number; + idVariante: number; idCotacao?: number; - unidade?: string; + + valorUnitario?: number; produto?: Produto; - insumo?: Insumo; + variante?: Variante; cotacao?: Cotacao; createdAt: Date; updatedAt: Date; diff --git a/src/modules/lista-insumos/lista-insumos.controller.ts b/src/modules/lista-insumos/lista-insumos.controller.ts index 59b64a9..85b9a31 100644 --- a/src/modules/lista-insumos/lista-insumos.controller.ts +++ b/src/modules/lista-insumos/lista-insumos.controller.ts @@ -1,54 +1,83 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe, UsePipes, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + ValidationPipe, + UsePipes, + Query, + Header, + Res, +} from '@nestjs/common'; import { ListaInsumosService } from './lista-insumos.service'; import { CreateListaInsumoDto } from './dto/create-lista-insumo.dto'; import { UpdateListaInsumoDto } from './dto/update-lista-insumo.dto'; -import { ApiTags } from '@nestjs/swagger'; -import {response as res} from 'express'; +import { ApiBody, ApiTags } from '@nestjs/swagger'; @ApiTags('lista-insumos') @Controller('lista-insumos') export class ListaInsumosController { constructor(private readonly listaInsumosService: ListaInsumosService) {} - @Get('paginate') - async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.listaInsumosService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.listaInsumosService.findAllWithPagination(page, perPage); - } - @UsePipes(ValidationPipe) + @Post() - create(@Body() createListaInsumoDto: CreateListaInsumoDto) { - return this.listaInsumosService.create(createListaInsumoDto); - } - - @Get() - findAll() { - return this.listaInsumosService.findAll(); + async create(@Body() createListaInsumoDto: CreateListaInsumoDto) { + return await this.listaInsumosService.create(createListaInsumoDto); } @Get('produtos/:id') - async findProdutoOrc(@Param('id') id: number) { - return await this.listaInsumosService.findInsumoProd(+id); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findProdutoOrc( + @Param('id') id: number, + @Query('page') page: number, + @Query('perPage') perPage: number, + @Query('titulo_like') titulo_like: string, + @Res({ passthrough: true }) res, + ) { + + page = page || 1; + perPage = perPage || 5; + + const listasinsumos = await this.listaInsumosService.findAllWithPagination( + +id, + page, + Number(perPage), + titulo_like, + ); + const total = await this.listaInsumosService.countAll(+id,titulo_like); + res.header('x-total-count', total); + return await listasinsumos; } @Get(':id') - findOne(@Param('id') id: string) { - return this.listaInsumosService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.listaInsumosService.findOne(+id); } + @Patch(':id') - update( + async update( @Param('id') id: string, @Body() updateListaInsumoDto: UpdateListaInsumoDto, ) { - return this.listaInsumosService.update(+id, updateListaInsumoDto); + return await this.listaInsumosService.update(+id, updateListaInsumoDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.listaInsumosService.remove(+id); + async remove(@Param('id') id: string) { + return await this.listaInsumosService.remove(+id); + } + + @Post(':id/cotar') + @ApiBody({}) + async selectCotacao(@Param('id') idItemListaInsumo: number, @Body() body) { + return await this.listaInsumosService.selectCotacao( + +idItemListaInsumo, + +body.idCotacao, + ); } } diff --git a/src/modules/lista-insumos/lista-insumos.module.ts b/src/modules/lista-insumos/lista-insumos.module.ts index 20a58c1..17081b2 100644 --- a/src/modules/lista-insumos/lista-insumos.module.ts +++ b/src/modules/lista-insumos/lista-insumos.module.ts @@ -1,10 +1,14 @@ import { Module } from '@nestjs/common'; import { ListaInsumosService } from './lista-insumos.service'; import { ListaInsumosController } from './lista-insumos.controller'; -import { PrismaService } from 'src/databases/prisma.service'; +import { ProdutosModule } from '../produtos/produtos.module'; +import { CotacoesModule } from '../cotacoes/cotacoes.module'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; +import { OrcamentosModule } from '../orcamentos/orcamentos.module'; @Module({ + imports: [PrismaModule, ProdutosModule, CotacoesModule,OrcamentosModule], controllers: [ListaInsumosController], - providers: [ListaInsumosService, PrismaService], + providers: [ListaInsumosService], }) export class ListaInsumosModule {} diff --git a/src/modules/lista-insumos/lista-insumos.service.ts b/src/modules/lista-insumos/lista-insumos.service.ts index ab90037..27d9400 100644 --- a/src/modules/lista-insumos/lista-insumos.service.ts +++ b/src/modules/lista-insumos/lista-insumos.service.ts @@ -1,45 +1,110 @@ import { Injectable } from '@nestjs/common'; import { CreateListaInsumoDto } from './dto/create-lista-insumo.dto'; import { UpdateListaInsumoDto } from './dto/update-lista-insumo.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { CotacoesService } from '../cotacoes/cotacoes.service'; +import { ListaInsumo } from './entities/lista-insumo.entity'; +import { ProdutosService } from '../produtos/produtos.service'; +import { OrcamentosService } from '../orcamentos/orcamentos.service'; @Injectable() export class ListaInsumosService { - constructor(private readonly prismaService: PrismaService) {} + constructor( + private readonly prismaService: PrismaService, + private readonly produtosServices: ProdutosService, + private readonly orcamentoService: OrcamentosService, + private readonly cotacaoServices: CotacoesService, + + ) {} - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination( + id: number, + page: number, + perPage: number, + titulo_like: string, + ) { const skip = (page - 1) * perPage; - const listaInsumos = await this.prismaService.listaInsumo.findMany({ - skip, - take: perPage, - }); - return { listaInsumos }; - } + let listainsumos = ListaInsumo['']; - async countAll(){ - return await this.prismaService.insumoProdutoBase.count(); - } - - async create(createListaInsumoDto: CreateListaInsumoDto) { - return await this.prismaService.listaInsumo.create({ - data: createListaInsumoDto, + listainsumos = await this.prismaService.listaInsumo.findMany({ + skip, + take: perPage, + where: { + idProduto: id, + OR: [ + { variante: { insumo: { titulo: { contains: titulo_like, mode: 'insensitive' } } } }, + { cotacao: { fornecedor: { nome: { contains: titulo_like , mode: 'insensitive'} } } }, + { + cotacao: { + fornecedor: { nomeFantasia: { contains: titulo_like, mode: 'insensitive' } }, + }, + }, + { + cotacao: { fornecedor: { razaoSocial: { contains: titulo_like, mode: 'insensitive' } } }, + }, + + { + variante: { + insumo: { categoria: { titulo: { contains: titulo_like, mode: 'insensitive' } } }, + }, + }, + ], + }, }); + return listainsumos; } - async findInsumoProd(id: number) { - const listaInsumosProd = await this.prismaService.listaInsumo.findMany({ + async countAll(id: number, titulo_like: string = '') { + return await this.prismaService.listaInsumo.count({ where: { idProduto: id, + OR: [ + { variante: { insumo: { titulo: { contains: titulo_like, mode: 'insensitive' } } } }, + { cotacao: { fornecedor: { nome: { contains: titulo_like , mode: 'insensitive'} } } }, + { + cotacao: { + fornecedor: { nomeFantasia: { contains: titulo_like, mode: 'insensitive' } }, + }, + }, + { + cotacao: { fornecedor: { razaoSocial: { contains: titulo_like, mode: 'insensitive' } } }, + }, + + { + variante: { + insumo: { categoria: { titulo: { contains: titulo_like, mode: 'insensitive' } } }, + }, + }, + ], }, }); + } - if (!listaInsumosProd) { - return { - data: { message: 'Não existem insumos cadastrados deste produto' }, - }; + async create(createListaInsumoDto: CreateListaInsumoDto) { + const insumoExists = await this.prismaService.variante.findFirst({ + where: { id: createListaInsumoDto.idVariante }, + }); + if (insumoExists) { + const produtoExists = await this.prismaService.produto.findFirst({ + where: { id: createListaInsumoDto.idProduto }, + }); + if (produtoExists) { + if (createListaInsumoDto.idCotacao) { + var cotacaoExists = await this.prismaService.cotacao.findFirst({ + where: { id: createListaInsumoDto.idCotacao }, + }); + } + if (cotacaoExists || !createListaInsumoDto.idCotacao) { + return await this.prismaService.listaInsumo.create({ + data: createListaInsumoDto, + }); + } + return { data: { message: 'Cotação não existe' } }; + } + return { data: { message: 'Produto não existe' } }; } - return listaInsumosProd; + return { data: { message: 'Insumo não existe' } }; } async findAll() { @@ -47,17 +112,67 @@ export class ListaInsumosService { } async findOne(id: number) { - return await this.prismaService.listaInsumo.findFirst({ where: { id } }); + + return await this.prismaService.listaInsumo.findFirst({ where: { id }}); } async update(id: number, updateListaInsumoDto: UpdateListaInsumoDto) { - return await this.prismaService.listaInsumo.update({ - where: { id }, - data: updateListaInsumoDto, + const insumoExists = await this.prismaService.variante.findFirst({ + where: { id: updateListaInsumoDto.idVariante }, }); + if (insumoExists) { + const produtoExists = await this.prismaService.produto.findFirst({ + where: { id: updateListaInsumoDto.idProduto }, + }); + if (produtoExists) { + if (updateListaInsumoDto.idCotacao) { + var cotacaoExists = await this.prismaService.cotacao.findFirst({ + where: { id: updateListaInsumoDto.idCotacao }, + }); + } + if (cotacaoExists || !updateListaInsumoDto.idCotacao) { + return await this.prismaService.listaInsumo.update({ + where: { id }, + data: updateListaInsumoDto, + }); + } + return { data: { message: 'Cotação não existe' } }; + } + return { data: { message: 'Produto não existe' } }; + } + return { data: { message: 'Insumo não existe' } }; } async remove(id: number) { - return await this.prismaService.listaInsumo.delete({ where: { id } }); + const listaInsumoExists = await this.findOne(id); + if (listaInsumoExists) { + return await this.prismaService.listaInsumo.delete({ + where: { id }, + }); + } + return { data: { message: 'Insumo da lista não existe' } }; + } + + async selectCotacao(idItemListaInsumo: number, idCotacao: number) { + const cotacao = await this.cotacaoServices.findOne(idCotacao); + + if (!cotacao) { + return { data: { message: 'Essa cotação não existe' } }; + } + + const listaInsumoExists = await this.findOne(idItemListaInsumo); + if (!listaInsumoExists) { + return { data: { message: 'Esse insumo não existe' } }; + } + listaInsumoExists.valorUnitario = cotacao.valor; + + listaInsumoExists.idCotacao = cotacao.id; + + const salvo = await this.update(listaInsumoExists.id, listaInsumoExists); + + const codOrc = await this.produtosServices.findOne(listaInsumoExists.idProduto) + + this.orcamentoService.recalcular(codOrc.idOrcamento, listaInsumoExists.idProduto); + return salvo; } } diff --git a/src/modules/orcamentos/dto/create-orcamento.dto.ts b/src/modules/orcamentos/dto/create-orcamento.dto.ts index 4a3055d..7dea2a7 100644 --- a/src/modules/orcamentos/dto/create-orcamento.dto.ts +++ b/src/modules/orcamentos/dto/create-orcamento.dto.ts @@ -1,6 +1,15 @@ import { ApiProperty } from '@nestjs/swagger'; import { status as Status } from '@prisma/client'; -import { IsDate, IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { + IsDateString, + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, + ValidateIf, + isNotEmpty, +} from 'class-validator'; export class CreateOrcamentoDto { @ApiProperty({ @@ -8,7 +17,8 @@ export class CreateOrcamentoDto { 'A validade serve para descrever até qual data o orçamento será valido', example: '2023-10-23T17:30:44.382Z', }) - @IsDate({message: 'Validade inserida não é uma data'}) + @IsOptional() + @IsDateString({},{ message: 'A validade inserida não é válida' }) validade?: Date; @ApiProperty({ @@ -16,7 +26,8 @@ export class CreateOrcamentoDto { 'O total mão de obra serve para descrever o custo total de mão de obra para produzir os itens do orçamento', example: '750', }) - @IsNumber({},{ message: 'Valor total de mão de obra deve ser um numero'}) + @IsOptional() + @IsNumber({}, { message: 'O valor de mão de obra inserido não é válido' }) totalMaoObra?: number; @ApiProperty({ @@ -24,15 +35,16 @@ export class CreateOrcamentoDto { 'O total materiais serve para descrever o custo total das compras do materiais para produzir os itens do orçamento', example: '700', }) - @IsNumber({},{ message: 'Valor total de materiais deve ser um numero'}) + @IsOptional() + @IsNumber({}, { message: 'O valor total de materiais inserido não é válido' }) totalMateriais?: number; @ApiProperty({ - description: - 'O status serve para descrever a atual situação do orçamento', + description: 'O status serve para descrever a atual situação do orçamento', example: 'Pendente', }) - @IsNotEmpty({ message: 'Status não pode ser vazio'}) + @IsNotEmpty({ message: 'O status não pode estar vazio' }) + @IsEnum(Status, { message: 'O status inserido não é válido' }) status: Status; @ApiProperty({ @@ -40,7 +52,8 @@ export class CreateOrcamentoDto { 'O prazo estimado de produção serve para descrever uma estimativa de quanto tempo será necessário para concluir o orçamento, descrito em dias', example: '90', }) - @IsNumber({},{ message: 'Prazo estimado deve ser um numero'}) + @IsNotEmpty({ message: 'Informe uma Valor em Dias' }) + @IsNumber({}, { message: 'O prazo estimado inserido não é válido' }) prazoEstimadoProducao: number; @ApiProperty({ @@ -48,7 +61,8 @@ export class CreateOrcamentoDto { 'As observações servem para descrever caracteristicas relevantes obre o orçamento', example: '2 portões e 1 grade para janela', }) - @IsString({ message: 'Observação não é de um tipo valido'}) + @IsOptional() + @IsString({ message: 'A observação inserida não é válida' }) observacoes?: string; @ApiProperty({ @@ -56,7 +70,11 @@ export class CreateOrcamentoDto { 'O id do cliente serve para indentificar qual o cliente a quem este orçamento pertence', example: '1', }) - @IsNotEmpty({ message: 'Cliente não pode ser vazio'}) - @IsNumber({},{ message: 'Id do cliente deve ser um numero'}) + @IsNotEmpty({ message: 'O cliente não pode estar vazio' }) + @IsNumber({}, { message: 'O cliente inserido não é válido' }) idCliente: number; + + @IsOptional() + @IsNumber({}, { message: 'O vendedor inserido não é válido' }) + criadorPor?: number; } diff --git a/src/modules/orcamentos/dto/update-orcamento.dto.ts b/src/modules/orcamentos/dto/update-orcamento.dto.ts index 8e1a004..459472c 100644 --- a/src/modules/orcamentos/dto/update-orcamento.dto.ts +++ b/src/modules/orcamentos/dto/update-orcamento.dto.ts @@ -2,7 +2,15 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateOrcamentoDto } from './create-orcamento.dto'; import { status as Status } from '@prisma/client'; import { ApiProperty } from '@nestjs/swagger'; -import { IsDate, IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { + IsDateString, + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, + ValidateIf, +} from 'class-validator'; export class UpdateOrcamentoDto extends PartialType(CreateOrcamentoDto) { @ApiProperty({ @@ -10,7 +18,8 @@ export class UpdateOrcamentoDto extends PartialType(CreateOrcamentoDto) { 'A validade serve para descrever até qual data o orçamento será valido', example: '2023-10-23T17:30:44.382Z', }) - @IsDate({message: 'Validade inserida não é uma data'}) + @IsOptional() + @IsDateString({},{ message: 'A validade inserida não é válida' }) validade?: Date; @ApiProperty({ @@ -18,7 +27,8 @@ export class UpdateOrcamentoDto extends PartialType(CreateOrcamentoDto) { 'O total mão de obra serve para descrever o custo total de mão de obra para produzir os itens do orçamento', example: '750', }) - @IsNumber({},{ message: 'Valor total de mao de obra deve ser um numero'}) + @ValidateIf((object, value) => value !== undefined) + @IsNumber({}, { message: 'O valor de mão de obra inserido não é válido' }) totalMaoObra?: number; @ApiProperty({ @@ -26,15 +36,16 @@ export class UpdateOrcamentoDto extends PartialType(CreateOrcamentoDto) { 'O total materiais serve para descrever o custo total das compras do materiais para produzir os itens do orçamento', example: '700', }) - @IsNumber({},{ message: 'Valor total de materiais deve ser um numero'}) + @IsOptional() + @IsNumber({}, { message: 'O valor total de materiais inserido não é válido' }) totalMateriais?: number; @ApiProperty({ - description: - 'O status serve para descrever a atual situação do orçamento', + description: 'O status serve para descrever a atual situação do orçamento', example: 'Pendente', }) - @IsNotEmpty({ message: 'Status não pode ser vazio'}) + @IsNotEmpty({ message: 'O status não pode estar vazio' }) + @IsEnum(Status, { message: 'O status inserido não é válido' }) status?: Status; @ApiProperty({ @@ -42,15 +53,17 @@ export class UpdateOrcamentoDto extends PartialType(CreateOrcamentoDto) { 'O prazo estimado de produção serve para descrever uma estimativa de quanto tempo será necessário para concluir o orçamento, descrito em dias', example: '90', }) - @IsNumber({},{ message: 'Prazo estimado deve ser um numero'}) + @IsNotEmpty({ message: 'Informe uma Valor em Dias' }) + @IsNumber({}, { message: 'O prazo estimado inserido não é válido' }) prazoEstimadoProducao?: number; - + @ApiProperty({ description: 'As observações servem para descrever caracteristicas relevantes obre o orçamento', example: '2 portões e 1 grade para janela', }) - @IsString({ message: 'Observação não é de um tipo valido'}) + @IsOptional() + @IsString({ message: 'A observação inserida não é válida' }) observacoes?: string; @ApiProperty({ @@ -58,7 +71,7 @@ export class UpdateOrcamentoDto extends PartialType(CreateOrcamentoDto) { 'O id do cliente serve para indentificar qual o cliente a quem este orçamento pertence', example: '1', }) - @IsNotEmpty({ message: 'Cliente não pode ser vazio'}) - @IsNumber({},{ message: 'Id do cliente deve ser um numero'}) + @IsNotEmpty({ message: 'O cliente não pode estar vazio' }) + @IsNumber({}, { message: 'O cliente inserido não é válido' }) idCliente?: number; } diff --git a/src/modules/orcamentos/entities/orcamento.entity.ts b/src/modules/orcamentos/entities/orcamento.entity.ts index d86868a..5625aa8 100644 --- a/src/modules/orcamentos/entities/orcamento.entity.ts +++ b/src/modules/orcamentos/entities/orcamento.entity.ts @@ -9,10 +9,10 @@ export class Orcamento { totalMaoObra?: number; totalMateriais?: number; status: status; - prazoEstimadoProducao: number; + criadorPor?: number; + prazoEstimadoProducao?: number; observacoes?: string; idCliente: number; - idPedido?: number; cliente: Cliente; createdAt: Date; updatedAt: Date; diff --git a/src/modules/orcamentos/orcamentos.controller.ts b/src/modules/orcamentos/orcamentos.controller.ts index 96605fd..fec2b10 100644 --- a/src/modules/orcamentos/orcamentos.controller.ts +++ b/src/modules/orcamentos/orcamentos.controller.ts @@ -1,9 +1,26 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe,UsePipes, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + ValidationPipe, + UsePipes, + Query, + Res, + Header, + Request, +} from '@nestjs/common'; import { OrcamentosService } from './orcamentos.service'; import { CreateOrcamentoDto } from './dto/create-orcamento.dto'; import { UpdateOrcamentoDto } from './dto/update-orcamento.dto'; import { ApiTags } from '@nestjs/swagger'; -import {response as res} from 'express'; +import { AuthRequest } from 'src/auth/models/AuthRequest'; +import { Usuario } from '../usuarios/entities/usuario.entity'; +import { CurrentUser } from 'src/auth/decorators/current-user.decorator'; + @ApiTags('orcamentos') @Controller('orcamentos') export class OrcamentosController { @@ -13,45 +30,65 @@ export class OrcamentosController { async countAll() { return await this.orcamentosService.countAll(); } - + @Get() - findAll() { - return this.orcamentosService.findAll(); - } - - @Get('paginate') - async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.orcamentosService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.orcamentosService.findAllWithPagination(page, perPage); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll( + + @Query('page') page: number, + @Query('perPage') perPage: number, + @Query('titulo_like') titulo_like: string, + @Res({ passthrough: true }) res, + ) { + page = page || 1; + perPage = perPage || (await this.countAll()); + + const orcamentos = await this.orcamentosService.findAllWithPagination( + page, + Number(perPage), + titulo_like, + ); + const total = await this.orcamentosService.countAll(titulo_like); + res.header('x-total-count', total); + return await orcamentos; } - - + @Post() - create(@Body() createOrcamentoDto: CreateOrcamentoDto) { - - return this.orcamentosService.create(createOrcamentoDto); + async create( + @CurrentUser() usuario: Usuario, + @Body() createOrcamentoDto: CreateOrcamentoDto, + ) { + return await this.orcamentosService.create(createOrcamentoDto, usuario); } + @Get('full/:id') + async findOneFull(@Param('id') id: string) { + return await this.orcamentosService.findOneFull(+id); + } + @Get('concluded') + async findAllconcluded() { + return await this.orcamentosService.findAllconcluded(); + } @Get(':id') - findOne(@Param('id') id: string) { - return this.orcamentosService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.orcamentosService.findOne(+id); } - @UsePipes(ValidationPipe) + @Patch(':id') - update( + async update( @Param('id') id: string, @Body() updateOrcamentoDto: UpdateOrcamentoDto, ) { - return this.orcamentosService.update(+id, updateOrcamentoDto); + return await this.orcamentosService.update(+id, updateOrcamentoDto); } - + @Delete(':id') - remove(@Param('id') id: string) { - return this.orcamentosService.remove(+id); + async remove(@Param('id') id: string) { + return await this.orcamentosService.remove(+id); } + + } diff --git a/src/modules/orcamentos/orcamentos.module.ts b/src/modules/orcamentos/orcamentos.module.ts index 7fbe11f..558a5b4 100644 --- a/src/modules/orcamentos/orcamentos.module.ts +++ b/src/modules/orcamentos/orcamentos.module.ts @@ -1,13 +1,16 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { OrcamentosService } from './orcamentos.service'; import { OrcamentosController } from './orcamentos.controller'; -import { PrismaService } from 'src/databases/prisma.service'; -import { ProdutosService } from '../produtos/produtos.service'; -import { ProdutosBaseService } from '../produtos-base/produtos-base.service'; -import { InsumosProdutosBaseService } from '../insumos-produtos-base/insumos-produtos-base.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; +import { ProdutosModule } from '../produtos/produtos.module'; @Module({ + imports: [ + ProdutosModule, + PrismaModule, + ], controllers: [OrcamentosController], - providers: [OrcamentosService, PrismaService, ProdutosService, ProdutosBaseService, InsumosProdutosBaseService], + providers: [OrcamentosService], + exports: [OrcamentosService], }) export class OrcamentosModule {} diff --git a/src/modules/orcamentos/orcamentos.service.ts b/src/modules/orcamentos/orcamentos.service.ts index 2d31b64..c946d44 100644 --- a/src/modules/orcamentos/orcamentos.service.ts +++ b/src/modules/orcamentos/orcamentos.service.ts @@ -1,32 +1,74 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, forwardRef } from '@nestjs/common'; import { CreateOrcamentoDto } from './dto/create-orcamento.dto'; import { UpdateOrcamentoDto } from './dto/update-orcamento.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; import { ProdutosService } from '../produtos/produtos.service'; +import { Orcamento } from './entities/orcamento.entity'; +import { Usuario } from '../usuarios/entities/usuario.entity'; @Injectable() export class OrcamentosService { - constructor(private readonly prismaService: PrismaService, private readonly produtoService: ProdutosService) {} - async countAll() { - return await this.prismaService.cliente.count({}); + constructor( + private readonly prismaService: PrismaService, + private readonly produtoService: ProdutosService, + ) {} + async countAll(titulo_like: string = '') { + return await this.prismaService.orcamento.count({ + where: { + OR: [ + { + id: isNaN(parseInt(titulo_like)) + ? undefined + : parseInt(titulo_like), + }, + { cliente: { nome: { contains: titulo_like } } }, + { cliente: { nomeFantasia: { contains: titulo_like } } }, + { cliente: { razaoSocial: { contains: titulo_like } } }, + ], + pedido: null, + }, + }); } async findCliente(id: number) { return await this.prismaService.cliente.findFirst({ where: { id } }); } - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination( + page: number, + perPage: number, + titulo_like: string = '', + ) { const skip = (page - 1) * perPage; - const orcamentos = await this.prismaService.orcamento.findMany({ - skip, - take: perPage, - }); - return { orcamentos }; + + let orcamentos; + + orcamentos = await this.prismaService.orcamento.findMany({ + skip, + take: perPage, + where: { + OR: [ + { + id: isNaN(parseInt(titulo_like)) + ? undefined + : parseInt(titulo_like), + }, + { cliente: { nome: { contains: titulo_like, mode: 'insensitive' } } }, + { cliente: { nomeFantasia: { contains: titulo_like, mode: 'insensitive' } } }, + { cliente: { razaoSocial: { contains: titulo_like , mode: 'insensitive'} } }, + ], + pedido: null, + }, + }); + + return orcamentos; } - async create(createOrcamentoDto: CreateOrcamentoDto) { + async create(createOrcamentoDto: CreateOrcamentoDto, usuario: Usuario) { + const clienteExists = await this.findCliente(createOrcamentoDto.idCliente); if (clienteExists) { + createOrcamentoDto.criadorPor = usuario.id; return await this.prismaService.orcamento.create({ data: createOrcamentoDto, }); @@ -34,12 +76,28 @@ export class OrcamentosService { return { data: { message: 'Cliente não existe' } }; } - async findAll() { - const orcamentos = await this.prismaService.orcamento.findMany(); - if (orcamentos) { + async findAllconcluded() { + const orcamentos = await this.prismaService.orcamento.findMany({ + where: { + status: 'Concluido', + pedido: null, + }, + include: { cliente: true }, + }); + + if (orcamentos.length > 0) { return orcamentos; } - return { data: { message: 'Não há orçamentos' } }; + + return { data: { message: 'Não há orçamentos concluídos sem pedidos' } }; + } + + async findOneFull(id: number) { + const orcamento = await this.prismaService.orcamento.findFirst({ + where: { id }, + include: { cliente: true, produtos: true }, + }); + return orcamento; } async findOne(id: number) { @@ -47,30 +105,66 @@ export class OrcamentosService { } async update(id: number, updateOrcamentoDto: UpdateOrcamentoDto) { - const orcamento = await this.prismaService.orcamento.update({ - where: { id }, - data: updateOrcamentoDto, - }); - - if (orcamento) { - return orcamento; + const orcamentoExists = await this.findOne(id); + if (orcamentoExists) { + const clienteExists = await this.findCliente( + updateOrcamentoDto.idCliente, + ); + if (clienteExists) { + if (orcamentoExists.status === 'Concluido') { + return await this.prismaService.orcamento.update({ + where: { id }, + data: { + status: updateOrcamentoDto.status, + }, + }); + } + return await this.prismaService.orcamento.update({ + where: { id }, + data: updateOrcamentoDto, + }); + } + return { data: { message: 'Cliente não existe' } }; } - - return { data: { message: 'Ocorreu um erro ao atualizar o orcamento' } }; + return { data: { message: 'Orçamento não existe' } }; } async remove(id: number) { - const produtos = await this.prismaService.produto.findMany({ - where: { - orcamentoId: id, - }, - }); - for (const produto of produtos) { - await this.produtoService.remove(produto.id) + const orcamentoExists = await this.findOne(id); + if (orcamentoExists) { + const produtos = await this.prismaService.produto.findMany({ + where: { + idOrcamento: id, + }, + }); + for (const produto of produtos) { + await this.produtoService.remove(produto.id); + } + const removeOrcamento = await this.prismaService.orcamento.delete({ + where: { id }, + }); + return { removeOrcamento }; } - const removeOrcamento = await this.prismaService.orcamento.delete({ - where: { id }, + return { data: { message: 'Orçamento não existe' } }; + } + + async recalcular(idOrcamento: number, idProduto: number) { + + await this.produtoService.recalcularValor(idProduto); + const produtos = await this.produtoService.findProdutoOrc(idOrcamento); + + const valorTotalMaterial = produtos.reduce( + (total, produto) => total + produto.valorMaterial * produto.quantidade, + 0, + ); + const valorTotalMaoDeObra = produtos.reduce( + (total, produto) => total + produto.valorMaoDeObra * produto.quantidade, + 0, + ); + + await this.update(idOrcamento, { + totalMateriais: valorTotalMaterial, + totalMaoObra: valorTotalMaoDeObra, }); - return { removeOrcamento }; } } diff --git a/src/modules/pedidos/dto/create-pedido.dto.ts b/src/modules/pedidos/dto/create-pedido.dto.ts index 4a3512b..94db963 100644 --- a/src/modules/pedidos/dto/create-pedido.dto.ts +++ b/src/modules/pedidos/dto/create-pedido.dto.ts @@ -1,24 +1,30 @@ import { ApiProperty } from '@nestjs/swagger'; import { status } from '@prisma/client'; +import { IsEnum, IsNotEmpty, IsNumber } from 'class-validator'; export class CreatePedidoDto { @ApiProperty({ description: 'O pagamento serve para descrever o quanto o cliente pagará no total para o orçamento', example: '2400', }) + @IsNotEmpty({ message: 'O pagamento não pode estar vazio' }) + @IsNumber({}, { message: 'O pagamento inserido não é válido' }) pagamento: number; - + @ApiProperty({ - description: - 'O status serve para descrever a atual situação do orçamento', + description: 'O status serve para descrever a atual situação do orçamento', example: 'Concluido', }) + @IsNotEmpty({ message: 'O status não pode estar vazio' }) + @IsEnum(status, { message: 'O status inserido não é válido' }) status: status; - + @ApiProperty({ description: 'O id do orçamento serve para descrever a qual orçamento este pedido pertence', example: '1', }) + @IsNotEmpty({ message: 'O orçamento não pode estar vazio' }) + @IsNumber({}, { message: 'O orçamento inserido não é válido' }) idOrcamento: number; } diff --git a/src/modules/pedidos/dto/update-pedido.dto.ts b/src/modules/pedidos/dto/update-pedido.dto.ts index 996fd5e..fc80c8a 100644 --- a/src/modules/pedidos/dto/update-pedido.dto.ts +++ b/src/modules/pedidos/dto/update-pedido.dto.ts @@ -1,26 +1,34 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreatePedidoDto } from './create-pedido.dto'; -import { status } from "@prisma/client"; +import { status } from '@prisma/client'; import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty, IsNumber } from 'class-validator'; export class UpdatePedidoDto extends PartialType(CreatePedidoDto) { - @ApiProperty({ - description: - 'O pagamento serve para descrever o quanto o cliente pagará no total para o orçamento', - example: '2400', - }) - pagamento?: number; - - @ApiProperty({ - description: - 'O status serve para descrever a atual situação do orçamento', - example: 'Concluido', - }) - status?: status; - - @ApiProperty({ - description: - 'O id do orçamento serve para descrever a qual orçamento este pedido pertence', - example: '1', - }) - idOrcamento?: number; + @ApiProperty({ + description: + 'O pagamento serve para descrever o quanto o cliente pagará no total para o orçamento', + example: '2400', + }) + @IsNotEmpty({ message: 'O pagamento não pode estar vazio' }) + @IsNumber({}, { message: 'O pagamento inserido não é válido' }) + pagamento: number; + + @ApiProperty({ + description: 'O status serve para descrever a atual situação do orçamento', + example: 'Concluido', + }) + @IsNotEmpty({ message: 'O status não pode estar vazio' }) + @IsEnum(status, { message: 'O status inserido não é válido' }) + status: status; + + @ApiProperty({ + description: + 'O id do orçamento serve para descrever a qual orçamento este pedido pertence', + example: '1', + }) + @IsNotEmpty({ message: 'O orçamento não pode estar vazio' }) + @IsNumber({}, { message: 'O orçamento inserido não é válido' }) + idOrcamento: number; + + } diff --git a/src/modules/pedidos/entities/pedido.entity.ts b/src/modules/pedidos/entities/pedido.entity.ts index 568bb5b..1c0a87d 100644 --- a/src/modules/pedidos/entities/pedido.entity.ts +++ b/src/modules/pedidos/entities/pedido.entity.ts @@ -1,12 +1,12 @@ -import { status } from "@prisma/client"; -import { Orcamento } from "../../orcamentos/entities/orcamento.entity"; +import { status } from '@prisma/client'; +import { Orcamento } from '../../orcamentos/entities/orcamento.entity'; export class Pedido { - id: number; - pagamento: number; - status: status; - idOrcamento: number; - orcamentos: Orcamento; - createdAt: Date; - updatedAt: Date; + id: number; + pagamento: number; + status: status; + idOrcamento: number; + orcamentos: Orcamento; + createdAt: Date; + updatedAt: Date; } diff --git a/src/modules/pedidos/pedidos.controller.ts b/src/modules/pedidos/pedidos.controller.ts index 47c377f..b7c0d7f 100644 --- a/src/modules/pedidos/pedidos.controller.ts +++ b/src/modules/pedidos/pedidos.controller.ts @@ -1,9 +1,21 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, UsePipes, ValidationPipe, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + UsePipes, + ValidationPipe, + Query, + Header, + Res, +} from '@nestjs/common'; import { PedidosService } from './pedidos.service'; import { CreatePedidoDto } from './dto/create-pedido.dto'; import { UpdatePedidoDto } from './dto/update-pedido.dto'; import { ApiTags } from '@nestjs/swagger'; -import {response as res} from 'express'; @ApiTags('pedidos') @Controller('pedidos') export class PedidosController { @@ -14,38 +26,41 @@ export class PedidosController { return await this.pedidosService.countAll(); } - @Get('paginate') - async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.pedidosService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.pedidosService.findAllWithPagination(page, perPage); - } - @UsePipes(ValidationPipe) + @Post() - create(@Body() createPedidoDto: CreatePedidoDto) { - return this.pedidosService.create(createPedidoDto); + async create(@Body() createPedidoDto: CreatePedidoDto) { + return await this.pedidosService.create(createPedidoDto); } @Get() - findAll() { - return this.pedidosService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('page') page: number,@Query('perPage') perPage: number,@Query('titulo_like') titulo_like : string, @Res({ passthrough: true }) res) { + page = page || 1; + perPage = perPage || await this.countAll(); + const pedidos = await this.pedidosService.findAllWithPagination( + page, + Number(perPage), + titulo_like + ); + const total = await this.pedidosService.countAll(titulo_like); + res.header('x-total-count',total); + return await pedidos } @Get(':id') - findOne(@Param('id') id: string) { - return this.pedidosService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.pedidosService.findOne(+id); } + - @UsePipes(ValidationPipe) @Patch(':id') - update(@Param('id') id: string, @Body() updatePedidoDto: UpdatePedidoDto) { - return this.pedidosService.update(+id, updatePedidoDto); + async update(@Param('id') id: string, @Body() updatePedidoDto: UpdatePedidoDto) { + return await this.pedidosService.update(+id, updatePedidoDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.pedidosService.remove(+id); + async remove(@Param('id') id: string) { + return await this.pedidosService.remove(+id); } } diff --git a/src/modules/pedidos/pedidos.module.ts b/src/modules/pedidos/pedidos.module.ts index a851cf0..e4a6323 100644 --- a/src/modules/pedidos/pedidos.module.ts +++ b/src/modules/pedidos/pedidos.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { PedidosService } from './pedidos.service'; import { PedidosController } from './pedidos.controller'; -import { PrismaService } from '../../databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [PedidosController], - providers: [PedidosService,PrismaService], + providers: [PedidosService], }) export class PedidosModule {} diff --git a/src/modules/pedidos/pedidos.service.ts b/src/modules/pedidos/pedidos.service.ts index e4dfaf3..3bb2ebb 100644 --- a/src/modules/pedidos/pedidos.service.ts +++ b/src/modules/pedidos/pedidos.service.ts @@ -1,30 +1,69 @@ import { Injectable } from '@nestjs/common'; import { CreatePedidoDto } from './dto/create-pedido.dto'; import { UpdatePedidoDto } from './dto/update-pedido.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { Pedido } from './entities/pedido.entity'; +import { Orcamento } from '../orcamentos/entities/orcamento.entity'; @Injectable() export class PedidosService { constructor(private readonly prismaService: PrismaService) {} - async countAll() { - return await this.prismaService.pedido.count({}); + async countAll(titulo_like: string = '') { + return await this.prismaService.pedido.count({ where:{ + OR: [ + + { orcamento:{ cliente: { nome: { contains: titulo_like, mode: 'insensitive' } }} }, + { orcamento:{ cliente: { razaoSocial: { contains: titulo_like , mode: 'insensitive'} }} }, + { orcamento:{ cliente: { nomeFantasia: { contains: titulo_like , mode: 'insensitive'} }} }, + ], + },}); } - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination(page: number, perPage: number, titulo_like? : string) { const skip = (page - 1) * perPage; - const pedidos = await this.prismaService.pedido.findMany({ - skip, - take: perPage, - }); - return { pedidos }; + let pedidos = Pedido[""]; + if(titulo_like){ + + pedidos = await this.prismaService.pedido.findMany({ + skip, + take: perPage, + where:{ + OR: [ + + { orcamento:{ cliente: { nome: { contains: titulo_like, mode: 'insensitive' } }} }, + { orcamento:{ cliente: { razaoSocial: { contains: titulo_like , mode: 'insensitive'} }} }, + { orcamento:{ cliente: { nomeFantasia: { contains: titulo_like , mode: 'insensitive'} }} }, + ], + }, + }); + }else{ + pedidos = await this.prismaService.pedido.findMany({ + skip, + take: perPage, + }); + } + return pedidos ; } + async create(createPedidoDto: CreatePedidoDto) { - const pedido = await this.findOne(createPedidoDto.idOrcamento); - if (!pedido) { - return await this.prismaService.pedido.create({ - data: createPedidoDto, - }); + const orcamentoExists = await this.prismaService.orcamento.findFirst({ + where: { + id: createPedidoDto.idOrcamento, + }, + }); + if (orcamentoExists) { + if (orcamentoExists.status === 'Concluido') { + const pedidoExists = await this.findOne(createPedidoDto.idOrcamento); + if (!pedidoExists) { + return await this.prismaService.pedido.create({ + data: createPedidoDto, + }); + } + return { data: { message: 'Pedido já existe' } }; + } + return { data: { message: 'Orçamento ainda não foi concluido' } }; } + return { data: { message: 'Orçamento não existe' } }; } async findAll() { @@ -32,17 +71,33 @@ export class PedidosService { } async findOne(id: number) { - return await this.prismaService.pedido.findFirst({ where: { id } }); + return await this.prismaService.pedido.findFirst({ where: { id }, include: {orcamento: true} }); } async update(id: number, updatePedidoDto: UpdatePedidoDto) { - return await this.prismaService.pedido.update({ - where: { id }, - data: updatePedidoDto, + const orcamentoExists = await this.prismaService.orcamento.findFirst({ + where: { + id: updatePedidoDto.idOrcamento, + }, }); + if (orcamentoExists) { + const pedidoExists = await this.findOne(id); + if (pedidoExists) { + return await this.prismaService.pedido.update({ + where: { id }, + data: updatePedidoDto, + }); + } + return { data: { message: 'Pedido não existe' } }; + } + return { data: { message: 'Orçamento não existe' } }; } async remove(id: number) { - return await this.prismaService.pedido.delete({ where: { id } }); + const pedidoExists = await this.findOne(id); + if (pedidoExists) { + return await this.prismaService.pedido.delete({ where: { id } }); + } + return { data: { message: 'Pedido não existe' } }; } } diff --git a/src/modules/produtos-base/dto/create-produtos-base.dto.ts b/src/modules/produtos-base/dto/create-produtos-base.dto.ts index c07bd4a..ab136aa 100644 --- a/src/modules/produtos-base/dto/create-produtos-base.dto.ts +++ b/src/modules/produtos-base/dto/create-produtos-base.dto.ts @@ -1,4 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsOptional, IsString, ValidateIf } from "class-validator"; export class CreateProdutosBaseDto { @ApiProperty({ @@ -6,6 +7,8 @@ export class CreateProdutosBaseDto { 'O titulo serve para identificar e pesquisar o produto base', example: 'Portão', }) + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) titulo: string; @ApiProperty({ @@ -13,5 +16,7 @@ export class CreateProdutosBaseDto { 'As observações servem para descrever caracteristicas relevantes sobre o produto base', example: '2" x 6 m', }) + @IsOptional() + @IsString({ message: 'A observação inserida não é válida' }) observacoes?: string; } diff --git a/src/modules/produtos-base/dto/update-produtos-base.dto.ts b/src/modules/produtos-base/dto/update-produtos-base.dto.ts index 5e4489f..3d280cd 100644 --- a/src/modules/produtos-base/dto/update-produtos-base.dto.ts +++ b/src/modules/produtos-base/dto/update-produtos-base.dto.ts @@ -1,6 +1,7 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateProdutosBaseDto } from './create-produtos-base.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator'; export class UpdateProdutosBaseDto extends PartialType(CreateProdutosBaseDto) { @ApiProperty({ @@ -8,12 +9,16 @@ export class UpdateProdutosBaseDto extends PartialType(CreateProdutosBaseDto) { 'O titulo serve para identificar e pesquisar o produto base', example: 'Portão', }) - titulo?: string; + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) + titulo: string; @ApiProperty({ description: 'As observações servem para descrever caracteristicas relevantes sobre o produto base', example: '2" x 6 m', }) + @IsOptional() + @IsString({ message: 'A observação inserida não é válida' }) observacoes?: string; } diff --git a/src/modules/produtos-base/produtos-base.controller.ts b/src/modules/produtos-base/produtos-base.controller.ts index e5f6428..f76c56f 100644 --- a/src/modules/produtos-base/produtos-base.controller.ts +++ b/src/modules/produtos-base/produtos-base.controller.ts @@ -1,49 +1,71 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe, UsePipes, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + ValidationPipe, + UsePipes, + Query, + Header, + Res, +} from '@nestjs/common'; import { ProdutosBaseService } from './produtos-base.service'; import { CreateProdutosBaseDto } from './dto/create-produtos-base.dto'; import { UpdateProdutosBaseDto } from './dto/update-produtos-base.dto'; import { ApiTags } from '@nestjs/swagger'; -import {response as res} from 'express'; + @ApiTags('produtos-base') @Controller('produtos-base') export class ProdutosBaseController { constructor(private readonly produtosBaseService: ProdutosBaseService) {} - @Get('paginate') - async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.produtosBaseService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.produtosBaseService.findAllWithPagination(page, perPage); - } + @Get('count') - countAll() { - return this.produtosBaseService.countAll(); + async countAll() { + return await this.produtosBaseService.countAll(); } - @UsePipes(ValidationPipe) + + @Post() - create(@Body() createProdutosBaseDto: CreateProdutosBaseDto) { - return this.produtosBaseService.create(createProdutosBaseDto); + async create(@Body() createProdutosBaseDto: CreateProdutosBaseDto) { + return await this.produtosBaseService.create(createProdutosBaseDto); } @Get() - findAll() { - return this.produtosBaseService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll(@Query('page') page: number,@Query('perPage') perPage: number,@Query('titulo_like') titulo_like : string, @Res({ passthrough: true }) res) { + page = page||1; + perPage = perPage||await this.countAll(); + const produtos = await this.produtosBaseService.findAllWithPagination( + page, + Number(perPage), + titulo_like + ); + const total = await this.produtosBaseService.countAll(titulo_like); + res.header('x-total-count',total); + return await produtos } @Get(':id') - findOne(@Param('id') id: string) { - return this.produtosBaseService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.produtosBaseService.findOne(+id); } - @UsePipes(ValidationPipe) + + @Patch(':id') - update(@Param('id') id: string, @Body() updateProdutosBaseDto: UpdateProdutosBaseDto) { - return this.produtosBaseService.update(+id, updateProdutosBaseDto); + async update( + @Param('id') id: string, + @Body() updateProdutosBaseDto: UpdateProdutosBaseDto, + ) { + return await this.produtosBaseService.update(+id, updateProdutosBaseDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.produtosBaseService.remove(+id); + async remove(@Param('id') id: string) { + return await this.produtosBaseService.remove(+id); } } diff --git a/src/modules/produtos-base/produtos-base.module.ts b/src/modules/produtos-base/produtos-base.module.ts index 8c39199..0268f00 100644 --- a/src/modules/produtos-base/produtos-base.module.ts +++ b/src/modules/produtos-base/produtos-base.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { ProdutosBaseService } from './produtos-base.service'; import { ProdutosBaseController } from './produtos-base.controller'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [ProdutosBaseController], - providers: [ProdutosBaseService, PrismaService], + providers: [ProdutosBaseService], + exports: [ProdutosBaseService], }) export class ProdutosBaseModule {} diff --git a/src/modules/produtos-base/produtos-base.service.ts b/src/modules/produtos-base/produtos-base.service.ts index a89fec1..83626b6 100644 --- a/src/modules/produtos-base/produtos-base.service.ts +++ b/src/modules/produtos-base/produtos-base.service.ts @@ -1,19 +1,36 @@ import { Injectable } from '@nestjs/common'; import { CreateProdutosBaseDto } from './dto/create-produtos-base.dto'; import { UpdateProdutosBaseDto } from './dto/update-produtos-base.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { ProdutosBase } from './entities/produtos-base.entity'; @Injectable() export class ProdutosBaseService { constructor(private readonly prismaService: PrismaService) {} - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination(page: number, perPage: number, titulo_like: string) { const skip = (page - 1) * perPage; - const produtosBase = await this.prismaService.produtoBase.findMany({ - skip, - take: perPage, - }); - return { produtosBase }; + let produtos = ProdutosBase[""]; + if(titulo_like){ + produtos = await this.prismaService.produtoBase.findMany({ + skip, + take: perPage, + where:{ + OR: [{ titulo: { contains: titulo_like , mode: 'insensitive'} }, + { observacoes: { contains: titulo_like, mode: 'insensitive' } }, + + ], + }, + }); + }else{ + produtos = await this.prismaService.produtoBase.findMany({ + skip, + take: perPage, + }); + } + + + return produtos ; } async findOneByTitle(titulo: string) { @@ -34,8 +51,13 @@ export class ProdutosBaseService { return { data: { message: 'Titulo ja cadastrado' } }; } - async countAll() { - return await this.prismaService.produtoBase.count(); + async countAll(titulo_like: string = '') { + return await this.prismaService.produtoBase.count({where:{ + OR: [{ titulo: { contains: titulo_like , mode: 'insensitive'} }, + { observacoes: { contains: titulo_like, mode: 'insensitive' } }, + + ], + },}); } async findAll() { @@ -47,22 +69,32 @@ export class ProdutosBaseService { } async update(id: number, updateProdutosBaseDto: UpdateProdutosBaseDto) { - return await this.prismaService.produtoBase.update({ - where: { id }, - data: updateProdutosBaseDto, - }); + const produtoBaseExists = await this.findOneByTitle( + updateProdutosBaseDto.titulo, + ); + if (!produtoBaseExists) { + return await this.prismaService.produtoBase.update({ + where: { id }, + data: updateProdutosBaseDto, + }); + } + return { data: { message: 'Titulo ja cadastrado' } }; } async remove(id: number) { - const removeInsumosBase = await this.prismaService.insumoProdutoBase.deleteMany({ - where: { - idProdutoBase: id, - }, - }); - const removeProdutoBase = await this.prismaService.produtoBase.delete({ - where: { id }, - }); + const produtoBaseExists = await this.findOne(id); + if (produtoBaseExists) { + const removeInsumosBase = + await this.prismaService.insumoProdutoBase.deleteMany({ + where: { + idProdutoBase: id, + }, + }); + const removeProdutoBase = await this.prismaService.produtoBase.delete({ + where: { id }, + }); - return{ removeProdutoBase, removeInsumosBase} + return { removeProdutoBase, removeInsumosBase }; + } } } diff --git a/src/modules/produtos/dto/addProdutoBase.dto.ts b/src/modules/produtos/dto/addProdutoBase.dto.ts index aea16f7..861476f 100644 --- a/src/modules/produtos/dto/addProdutoBase.dto.ts +++ b/src/modules/produtos/dto/addProdutoBase.dto.ts @@ -1,10 +1,13 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, ValidateIf } from 'class-validator'; export class addProdutoBaseDto { @ApiProperty({ - description: 'O titulo serve para identificar o produto', - example: 'Portão', + description: 'O id serve para apontar para um produto base', + example: '1', }) + @IsNotEmpty({message: 'O id do produto base não deve estar vazio'}) + @IsNumber({}, {message: 'O id do produto base inserido não é válido'}) id: number; @ApiProperty({ @@ -12,6 +15,8 @@ export class addProdutoBaseDto { 'A quantidade serve para descrever quantas unidades deste produto serão necessárias para o orçamento', example: '3', }) + @IsNotEmpty({message: 'A quantidade do produto base não deve estar vazio'}) + @IsNumber({}, {message: 'A quantidade inserida não é válida'}) quantidade: number; @ApiProperty({ @@ -19,6 +24,8 @@ export class addProdutoBaseDto { 'As observações servem para descrever caracteristicas relevantes sobre o produto', example: '2" x 6 m', }) + @IsOptional() + @IsString({message: 'A observação inserida não é válida'}) observacoes?: string; @ApiProperty({ @@ -26,5 +33,7 @@ export class addProdutoBaseDto { 'O id do orçamento serve para indentificar qual o orçamento a quem este produto pertence', example: '1', }) + @IsNotEmpty({message: 'O id do orçamento deve estar vazio'}) + @IsNumber({}, {message: 'O id do orçamento inserido não é válido'}) orcamentoId: number; } diff --git a/src/modules/produtos/dto/create-produto.dto.ts b/src/modules/produtos/dto/create-produto.dto.ts index ca3d17c..44ea743 100644 --- a/src/modules/produtos/dto/create-produto.dto.ts +++ b/src/modules/produtos/dto/create-produto.dto.ts @@ -1,38 +1,53 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, ValidateIf, isNotEmpty } from 'class-validator'; export class CreateProdutoDto { @ApiProperty({ - description: - 'O titulo serve para identificar o produto', + description: 'O titulo serve para identificar o produto', example: 'Portão', }) + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) titulo: string; - + @ApiProperty({ description: 'A quantidade serve para descrever quantas unidades deste produto serão necessárias para o orçamento', example: '3', }) + @IsNotEmpty({ message: 'Informe a Quantidade do Produto' }) + @IsNumber({}, { message: 'A quantidade inserida não é válida' }) quantidade: number; - + @ApiProperty({ description: 'O valor unitario serve para descrever o valor do produto como uma unica unidade', example: '340', }) + @ValidateIf((object, value) => value !== undefined) + @IsNumber({}, { message: 'O valor unitário inserido não é válido' }) valorUnitario?: number; - + @ApiProperty({ description: 'As observações servem para descrever caracteristicas relevantes sobre o produto', example: '2" x 6 m', }) + @ValidateIf((object, value) => value !== undefined) + @IsString({ message: 'A observação inserida não é válida' }) observacoes?: string; - + @ApiProperty({ description: 'O id do orçamento serve para indentificar qual o orçamento a quem este produto pertence', example: '1', }) - orcamentoId: number; + @IsNotEmpty({ message: 'O orçamento não pode estar vazio' }) + @IsNumber({}, { message: 'O orçamento inserido não é válido' }) + idOrcamento: number; + + + + valorMaterial?: number; + valorMaoDeObra?: number; } diff --git a/src/modules/produtos/dto/update-produto.dto.ts b/src/modules/produtos/dto/update-produto.dto.ts index cc1efa9..78c7538 100644 --- a/src/modules/produtos/dto/update-produto.dto.ts +++ b/src/modules/produtos/dto/update-produto.dto.ts @@ -1,40 +1,57 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateProdutoDto } from './create-produto.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, ValidateIf } from 'class-validator'; export class UpdateProdutoDto extends PartialType(CreateProdutoDto) { @ApiProperty({ - description: - 'O titulo serve para identificar o produto', + description: 'O titulo serve para identificar o produto', example: 'Portão', }) + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) titulo?: string; - + @ApiProperty({ description: 'A quantidade serve para descrever quantas unidades deste produto serão necessárias para o orçamento', example: '3', }) + @IsNotEmpty({ message: 'Informe a Quantidade' }) + @IsNumber({}, { message: 'A quantidade inserida não é válida' }) quantidade?: number; - + @ApiProperty({ description: 'O valor unitario serve para descrever o valor do produto como uma unica unidade', example: '340', }) + @IsOptional() + @IsNumber({}, { message: 'O valor unitário inserido não é válido' }) valorUnitario?: number; - + @ApiProperty({ description: 'As observações servem para descrever caracteristicas relevantes sobre o produto', example: '2" x 6 m', }) + @IsOptional() + @IsString({ message: 'A observação inserida não é válida' }) observacoes?: string; - + @ApiProperty({ description: 'O id do orçamento serve para indentificar qual o orçamento a quem este produto pertence', example: '1', }) - orcamentoId?: number; + @IsNotEmpty({ message: 'O orçamento não pode estar vazio' }) + @IsNumber({}, { message: 'O orçamento inserido não é válido' }) + idOrcamento?: number; + + @IsOptional() + valorMaterial?: number; + + + @IsOptional() + valorMaoDeObra?: number; } diff --git a/src/modules/produtos/entities/produto.entity.ts b/src/modules/produtos/entities/produto.entity.ts index 85f3b70..3d6fd82 100644 --- a/src/modules/produtos/entities/produto.entity.ts +++ b/src/modules/produtos/entities/produto.entity.ts @@ -4,9 +4,11 @@ import { Orcamento } from 'src/modules/orcamentos/entities/orcamento.entity'; export class Produto { id: number; titulo: string; - quantidade?: number; + quantidade: number; valorUnitario?: number; - observacoes: string; + valorMaterial?: number; + valorMaoDeObra?: number; + observacoes?: string; listaInsumos: ListaInsumo[]; createdAt: Date; updatedAt: Date; diff --git a/src/modules/produtos/produtos.controller.ts b/src/modules/produtos/produtos.controller.ts index 3c7fb22..b856fa2 100644 --- a/src/modules/produtos/produtos.controller.ts +++ b/src/modules/produtos/produtos.controller.ts @@ -9,72 +9,77 @@ import { UsePipes, ValidationPipe, Query, + Header, + Res, } from '@nestjs/common'; import { ProdutosService } from './produtos.service'; import { CreateProdutoDto } from './dto/create-produto.dto'; import { UpdateProdutoDto } from './dto/update-produto.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from 'express'; import { addProdutoBaseDto } from './dto/addProdutoBase.dto'; @ApiTags('produtos') @Controller('produtos') export class ProdutosController { constructor(private readonly produtosService: ProdutosService) {} - + @Post('addProdutoBase') - createProdFromBase(@Body() addProdutoBaseDto: addProdutoBaseDto) { - return this.produtosService.pullProdBase(addProdutoBaseDto); + async createProdFromBase(@Body() addProdutoBaseDto: addProdutoBaseDto) { + return await this.produtosService.pullProdBase(addProdutoBaseDto); } - @Get('paginate') - async findAllWithPagination( - @Query('page') page: number, - @Query('perPage') perPage: number, - ) { - page = page; - perPage = perPage; - const totalcount = await this.produtosService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.produtosService.findAllWithPagination(page, perPage); - } @Get('count') - countAll() { - return this.produtosService.countAll(); + async countAll(id:number) { + return await this.produtosService.countAll(id); } - @UsePipes(ValidationPipe) + @Post() - create(@Body() createProdutoDto: CreateProdutoDto) { - return this.produtosService.create(createProdutoDto); + async create(@Body() createProdutoDto: CreateProdutoDto) { + + return await this.produtosService.create(createProdutoDto); } @Get('prodOrc/:id') - findProdutoOrc(@Param('id') id: number) { - return this.produtosService.findProdutoOrc(+id); + async findProdutoOrc(@Param('id') id: number) { + return await this.produtosService.findProdutoOrc(+id); } @Get(':id') - findOne(@Param('id') id: string) { - return this.produtosService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.produtosService.findOne(+id); } @Get(':busca') - findManyByTitle(@Param('titulo') buscaparam: string) { - return this.produtosService.findManyByTitle(buscaparam); + async findManyByTitle(@Param('titulo') buscaparam: string) { + return await this.produtosService.findManyByTitle(buscaparam); } - @Get() - findAll() { - return this.produtosService.findAll(); + @Get(":id/produtos") + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll( @Param('id') id: number,@Query('page') page: number,@Query('perPage') perPage: number,@Query('titulo_like') titulo_like : string, @Res({ passthrough: true }) res) { + page = page||1; + perPage = perPage||await this.countAll(+id); + const produtos = await this.produtosService.findAllWithPagination( + +id, + page, + Number(perPage), + titulo_like + ); + const total = await this.produtosService.countAll(+id,titulo_like); + res.header('x-total-count',total); + return await produtos } - @UsePipes(ValidationPipe) + @Patch(':id') - update(@Param('id') id: string, @Body() updateProdutoDto: UpdateProdutoDto) { - return this.produtosService.update(+id, updateProdutoDto); + async update(@Param('id') id: string, @Body() updateProdutoDto: UpdateProdutoDto) { + return await this.produtosService.update(+id, updateProdutoDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.produtosService.remove(+id); + async remove(@Param('id') id: string) { + return await this.produtosService.remove(+id); } + + } diff --git a/src/modules/produtos/produtos.module.ts b/src/modules/produtos/produtos.module.ts index 18bd5af..a570b0f 100644 --- a/src/modules/produtos/produtos.module.ts +++ b/src/modules/produtos/produtos.module.ts @@ -1,13 +1,18 @@ import { Module } from '@nestjs/common'; import { ProdutosService } from './produtos.service'; import { ProdutosController } from './produtos.controller'; -import { PrismaService } from 'src/databases/prisma.service'; -import { ProdutosBaseService } from '../produtos-base/produtos-base.service'; -import { InsumoProdutosBase } from '../insumos-produtos-base/entities/insumo-produtos-base.entity'; -import { InsumosProdutosBaseService } from '../insumos-produtos-base/insumos-produtos-base.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; +import { ProdutosBaseModule } from '../produtos-base/produtos-base.module'; +import { InsumosProdutosBaseModule } from '../insumos-produtos-base/insumos-produtos-base.module'; @Module({ + imports: [ + PrismaModule, + ProdutosBaseModule, + InsumosProdutosBaseModule, + ], controllers: [ProdutosController], - providers: [ProdutosService, PrismaService, ProdutosBaseService, InsumosProdutosBaseService], + providers: [ProdutosService], + exports: [ProdutosService], }) export class ProdutosModule {} diff --git a/src/modules/produtos/produtos.service.ts b/src/modules/produtos/produtos.service.ts index 5bbcd13..3d8434c 100644 --- a/src/modules/produtos/produtos.service.ts +++ b/src/modules/produtos/produtos.service.ts @@ -1,10 +1,11 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, forwardRef } from '@nestjs/common'; import { CreateProdutoDto } from './dto/create-produto.dto'; import { UpdateProdutoDto } from './dto/update-produto.dto'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; import { ProdutosBaseService } from '../produtos-base/produtos-base.service'; import { InsumosProdutosBaseService } from '../insumos-produtos-base/insumos-produtos-base.service'; import { addProdutoBaseDto } from './dto/addProdutoBase.dto'; +import { Produto } from './entities/produto.entity'; @Injectable() export class ProdutosService { @@ -14,14 +15,27 @@ export class ProdutosService { private readonly insumosProdutosBaseService: InsumosProdutosBaseService, ) {} - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination( + id: number, + page: number, + perPage: number, + titulo_like: string, + ) { const skip = (page - 1) * perPage; - const produtos = await this.prismaService.produto.findMany({ + + let produtos = Produto['']; + + produtos = await this.prismaService.produto.findMany({ skip, take: perPage, + where: { + idOrcamento: id, + OR: [{ titulo: { contains: titulo_like, mode: 'insensitive' } }], + }, }); - return { produtos }; + return produtos; } + async findOneByTitle(titulo: string) { return await this.prismaService.produto.findFirst({ where: { titulo }, @@ -35,24 +49,36 @@ export class ProdutosService { } async create(createProdutoDto: CreateProdutoDto) { - const produtoExiste = await this.findOneByTitle(createProdutoDto.titulo); - if (!produtoExiste) { - return await this.prismaService.produto.create({ - data: createProdutoDto, - }); + const orcamentoExists = await this.prismaService.orcamento.findFirst({ + where: { id: createProdutoDto.idOrcamento }, + }); + if (orcamentoExists) { + const produtoExiste = await this.findOneByTitle(createProdutoDto.titulo); + if (!produtoExiste) { + return await this.prismaService.produto.create({ + data: createProdutoDto, + }); + } + return { data: { message: 'Titulo já cadastrado' } }; } + return { data: { message: 'Orçamento não existe' } }; } async findProdutoOrc(id: number) { return await this.prismaService.produto.findMany({ where: { - OR: [{ orcamentoId: { equals: id } }], + OR: [{ idOrcamento: { equals: id } }], }, }); } - async countAll() { - return await this.prismaService.produto.count(); + async countAll(id: number, titulo_like: string = '') { + return await this.prismaService.produto.count({ + where: { + idOrcamento: id, + OR: [{ titulo: { contains: titulo_like, mode: 'insensitive' } }], + }, + }); } async findAll() { @@ -64,54 +90,112 @@ export class ProdutosService { } async update(id: number, updateProdutoDto: UpdateProdutoDto) { - return await this.prismaService.produto.update({ - where: { id }, - data: updateProdutoDto, + const orcamentoExists = await this.prismaService.orcamento.findFirst({ + where: { id: updateProdutoDto.idOrcamento }, }); + if (orcamentoExists) { + return await this.prismaService.produto.update({ + where: { id }, + data: updateProdutoDto, + }); + } + return { data: { message: 'Orçamento não existe' } }; } async remove(id: number) { - const removeInsumos = await this.prismaService.listaInsumo.deleteMany({ - where: { - idProduto: id, - }, - }); - const removeProduto = await this.prismaService.produto.delete({ - where: { id }, - }); + const produtoExists = await this.findOne(id); + if (produtoExists) { + const removeInsumos = await this.prismaService.listaInsumo.deleteMany({ + where: { + idProduto: id, + }, + }); + const removeProduto = await this.prismaService.produto.delete({ + where: { id }, + }); - return { removeProduto, removeInsumos }; + return { removeProduto, removeInsumos }; + } + return { data: { message: 'Produto não existe' } }; } async pullProdBase(addProdutoBaseDto: addProdutoBaseDto) { const prodBase = await this.produtosBaseService.findOne( addProdutoBaseDto.id, ); + if (prodBase) { + const orcamentoExists = await this.prismaService.orcamento.findFirst({ + where: { id: addProdutoBaseDto.orcamentoId }, + }); + if (orcamentoExists) { + const insumosBase = + await this.insumosProdutosBaseService.findInsumoProdBase( + addProdutoBaseDto.id, + ); + + const copyProd = await this.prismaService.produto.create({ + data: { + titulo: prodBase.titulo, + idOrcamento: addProdutoBaseDto.orcamentoId, + observacoes: addProdutoBaseDto.observacoes, + quantidade: addProdutoBaseDto.quantidade, + }, + }); + + for (const insumoBase of insumosBase) { + await this.prismaService.listaInsumo.create({ + data: { + quantidade: insumoBase.quantidade, + idVariante: insumoBase.idVariante, + idProduto: copyProd.id, + }, + }); + } + + return copyProd; + } + return { data: { message: 'Orçamento não existe' } }; + } + return { data: { message: 'Produto base não existe' } }; + } - const insumosBase = - await this.insumosProdutosBaseService.findInsumoProdBase( - addProdutoBaseDto.id, - ); - - const copyProd = await this.prismaService.produto.create({ - data: { - titulo: prodBase.titulo, - orcamentoId: addProdutoBaseDto.orcamentoId, - observacoes: addProdutoBaseDto.observacoes, - quantidade: addProdutoBaseDto.quantidade, + async recalcularValor(id: number) { + const listaInsumosMeterial = await this.prismaService.listaInsumo.findMany({ + where: { + variante: { insumo: { categoria: { tipo: 'Insumo' } } }, + idProduto: id, + }, + select: { + valorUnitario: true, + quantidade: true, }, }); - for (const insumoBase of insumosBase) { - await this.prismaService.listaInsumo.create({ - data: { - quantidade: insumoBase.quantidade, - idInsumo: insumoBase.idInsumo, - idProduto: copyProd.id, - }, - }); - } + const ValorTotalMaterial = listaInsumosMeterial.reduce( + (total, insumo) => total + insumo.valorUnitario * insumo.quantidade, + 0, + ); + + const listaInsumosServico = await this.prismaService.listaInsumo.findMany({ + where: { + variante: { insumo: { categoria: { tipo: 'Mão de Obra' } } }, + idProduto: id, + }, + select: { + valorUnitario: true, + quantidade: true, + }, + }); - return copyProd; + const ValorTotalMaoDeObra = listaInsumosServico.reduce( + (total, insumo) => total + insumo.valorUnitario * insumo.quantidade, + 0, + ); + + await this.update(id, { + valorMaoDeObra: ValorTotalMaoDeObra, + valorMaterial: ValorTotalMaterial, + valorUnitario: ValorTotalMaterial + ValorTotalMaoDeObra, + }); } } diff --git a/src/modules/usuarios/dto/changePassword.dto.ts b/src/modules/usuarios/dto/changePassword.dto.ts new file mode 100644 index 0000000..296e378 --- /dev/null +++ b/src/modules/usuarios/dto/changePassword.dto.ts @@ -0,0 +1,26 @@ +import { + IsString, + MinLength, + MaxLength, + Matches, + Equals, + ValidateIf, + IsEmpty, +} from 'class-validator'; + +export class ChangePasswordDto { + @IsString() + @MinLength(4) + @MaxLength(20) + @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { + message: 'A senha é muito fraca', + }) + password: string; + + @IsString() + @ValidateIf((dto, conf) => conf !== dto.password) + @IsEmpty({ + message: 'As senhas não coincidem', + }) + confpassword: string; +} diff --git a/src/modules/usuarios/dto/create-usuario.dto.ts b/src/modules/usuarios/dto/create-usuario.dto.ts index 357f6c2..9a7521e 100644 --- a/src/modules/usuarios/dto/create-usuario.dto.ts +++ b/src/modules/usuarios/dto/create-usuario.dto.ts @@ -1,46 +1,42 @@ import { ApiProperty } from '@nestjs/swagger'; import { tipoUsuario } from '@prisma/client'; +import { + IsEmail, + IsEnum, + IsNotEmpty, + IsNumberString, + IsString, + Matches, + MaxLength, + MinLength, + ValidateIf, +} from 'class-validator'; +import { Usuario } from '../entities/usuario.entity'; export class CreateUsuarioDto { - @ApiProperty({ - description: - 'O tipo de usuario serve para descrever o nivel de acesso dele', - example: 'Vendedor', - }) + @IsNotEmpty({ message: 'O tipo do usuário não poder estar vazio' }) + @IsEnum(tipoUsuario, { message: 'O tipo de usuário inserido não é válido' }) tipoUsuario: tipoUsuario; - @ApiProperty({ - description: - 'O nome do usuário serve para identificar e pesquisar o usuário', - example: 'Sérgio Moraes', - }) + @IsString({ message: 'O nome inserido não é válido' }) nome: string; - @ApiProperty({ - description: 'O CPF serve para identificar o usuario', - example: '02370334029', - }) + @IsNumberString({}, { message: 'O CPF inserido não é válido' }) cpf: string; - @ApiProperty({ - description: 'O email serve para descrever o email do usuario', - example: 'email@gmail.com', - }) + @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) + @IsEmail({}, { message: 'O e-mail inserido não é válido' }) email: string; - @ApiProperty({ - description: - 'O telefone serve para descrever o numero de telefone do usuario', - example: '1734112736', - }) + @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) + @IsNumberString({}, { message: 'O telefone inserido não é válido' }) telefone: string; - @ApiProperty({ - description: - 'A senha serve para o usuário realizar o acesso dentro da aplicação', - example: '1234', + @IsString() + @MinLength(4) + @MaxLength(20) + @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { + message: 'password too weak', }) senha: string; - - token: string; } diff --git a/src/modules/usuarios/dto/update-usuario.dto.ts b/src/modules/usuarios/dto/update-usuario.dto.ts index 052ee80..d126134 100644 --- a/src/modules/usuarios/dto/update-usuario.dto.ts +++ b/src/modules/usuarios/dto/update-usuario.dto.ts @@ -2,6 +2,7 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateUsuarioDto } from './create-usuario.dto'; import { tipoUsuario } from '@prisma/client'; import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsString, Matches, ValidateIf } from 'class-validator'; export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) { @ApiProperty({ @@ -9,6 +10,8 @@ export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) { 'O tipo de usuario serve para descrever o nivel de acesso dele', example: 'Vendedor', }) + @IsNotEmpty({message: 'O tipo do usuário não poder estar vazio'}) + @IsEnum(tipoUsuario, {message: 'O tipo de usuário inserido não é válido'}) tipoUsuario?: tipoUsuario; @ApiProperty({ @@ -16,18 +19,24 @@ export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) { 'O nome do usuário serve para identificar e pesquisar o usuário', example: 'Sérgio Moraes', }) + @ValidateIf((object, value) => value !== undefined) + @IsString({ message: 'O nome inserido não é válido' }) nome?: string; @ApiProperty({ description: 'O CPF serve para identificar o usuario', example: '02370334029', }) + @ValidateIf((object, value) => value !== undefined) + @IsNumberString({}, { message: 'O CPF inserido não é válido' }) cpf?: string; @ApiProperty({ description: 'O email serve para descrever o email do usuario', example: 'email@gmail.com', }) + @IsNotEmpty({ message: 'O e-mail não pode estar vazio' }) + @IsEmail({}, { message: 'O e-mail inserido não é válido' }) email?: string; @ApiProperty({ @@ -35,6 +44,8 @@ export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) { 'O telefone serve para descrever o numero de telefone do usuario', example: '1734112736', }) + @IsNotEmpty({ message: 'O telefone não pode estar vazio' }) + @IsNumberString({}, { message: 'O telefone inserido não é válido' }) telefone?: string; @ApiProperty({ @@ -42,7 +53,9 @@ export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) { 'A senha serve para o usuário realizar o acesso dentro da aplicação', example: '1234', }) + @ValidateIf((object, value) => value !== undefined) + @IsString({message: 'A senha inserida não é válida'}) senha?: string; + - token: string; } diff --git a/src/modules/usuarios/entities/usuario.entity.ts b/src/modules/usuarios/entities/usuario.entity.ts index acb0217..8e05efd 100644 --- a/src/modules/usuarios/entities/usuario.entity.ts +++ b/src/modules/usuarios/entities/usuario.entity.ts @@ -8,7 +8,6 @@ export class Usuario { email: string; telefone: string; senha: string; - token: string; createdAt: Date; updatedAt: Date; } diff --git a/src/modules/usuarios/usuarios.controller.ts b/src/modules/usuarios/usuarios.controller.ts index bfdfbe4..f121e79 100644 --- a/src/modules/usuarios/usuarios.controller.ts +++ b/src/modules/usuarios/usuarios.controller.ts @@ -1,50 +1,93 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe,UsePipes, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + ValidationPipe, + UsePipes, + Query, + Header, + Res, +} from '@nestjs/common'; import { UsuariosService } from './usuarios.service'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { UpdateUsuarioDto } from './dto/update-usuario.dto'; import { ApiTags } from '@nestjs/swagger'; -import { response as res } from "express"; +import { CurrentUser } from 'src/auth/decorators/current-user.decorator'; +import { Usuario } from './entities/usuario.entity'; +import { ChangePasswordDto } from './dto/changePassword.dto'; + @ApiTags('usuarios') @Controller('usuarios') export class UsuariosController { constructor(private readonly usuariosService: UsuariosService) {} - @Get('paginate') - async findAllWithPagination(@Query('page') page: number, @Query('perPage') perPage: number) { - page = page; - perPage = perPage; - const totalcount = await this.usuariosService.countAll(); - res.set('x-total-count', totalcount.toString()); - return await this.usuariosService.findAllWithPagination(page, perPage); - } - @Get('count') - countAll() { - return this.usuariosService.countAll(); + async countAll() { + return await this.usuariosService.countAll(); } - @UsePipes(ValidationPipe) + + @Post() - create(@Body() createUsuarioDto: CreateUsuarioDto) { - return this.usuariosService.create(createUsuarioDto); + async create(@Body() createUsuarioDto: CreateUsuarioDto) { + return await this.usuariosService.create(createUsuarioDto); + } + + @Post(':id') + async changePassword( + @Param('id') id: string, + @Body() changePassword: ChangePasswordDto, + ) { + return await this.usuariosService.changePassword(+id, changePassword); } @Get() - findAll() { - return this.usuariosService.findAll(); + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll( + @CurrentUser() usuario: Usuario, + @Query('page') page: number, + @Query('perPage') perPage: number, + @Query('titulo_like') titulo_like: string, + @Res({ passthrough: true }) res, + ) { + + + const user:Usuario = await this.findOne(""+usuario.id) + + if (user.tipoUsuario != "Administrador") { + return await this.usuariosService.findManyByEmail(usuario.email); + } + page = page || 1; + perPage = perPage || (await this.countAll()); + const usuarios = await this.usuariosService.findAllWithPagination( + page, + Number(perPage), + titulo_like, + ); + const total = await this.usuariosService.countAll(titulo_like); + res.header('x-total-count', total); + return await usuarios; } @Get(':id') - findOne(@Param('id') id: string) { - return this.usuariosService.findOne(+id); + async findOne(@Param('id') id: string) { + return await this.usuariosService.findOne(+id); } @Patch(':id') - update(@Param('id') id: string, @Body() updateUsuarioDto: UpdateUsuarioDto) { - return this.usuariosService.update(+id, updateUsuarioDto); + async update( + @Param('id') id: string, + @Body() updateUsuarioDto: UpdateUsuarioDto, + ) { + return await this.usuariosService.update(+id, updateUsuarioDto); } @Delete(':id') - remove(@Param('id') id: string) { - return this.usuariosService.remove(+id); + async remove(@Param('id') id: string) { + return await this.usuariosService.remove(+id); } } diff --git a/src/modules/usuarios/usuarios.module.ts b/src/modules/usuarios/usuarios.module.ts index 50b2c1d..e5463ef 100644 --- a/src/modules/usuarios/usuarios.module.ts +++ b/src/modules/usuarios/usuarios.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { UsuariosService } from './usuarios.service'; import { UsuariosController } from './usuarios.controller'; -import { PrismaService } from 'src/databases/prisma.service'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [UsuariosController], - providers: [UsuariosService, PrismaService], + providers: [UsuariosService], + exports: [UsuariosService], }) export class UsuariosModule {} diff --git a/src/modules/usuarios/usuarios.service.ts b/src/modules/usuarios/usuarios.service.ts index 046c426..c42b518 100644 --- a/src/modules/usuarios/usuarios.service.ts +++ b/src/modules/usuarios/usuarios.service.ts @@ -1,44 +1,91 @@ import { Injectable } from '@nestjs/common'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { UpdateUsuarioDto } from './dto/update-usuario.dto'; -import { PrismaService } from '../../databases/prisma.service'; +import { Usuario } from './entities/usuario.entity'; + +import * as bcrypt from 'bcrypt'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { ChangePasswordDto } from './dto/changePassword.dto'; @Injectable() export class UsuariosService { constructor(private readonly prismaService: PrismaService) {} - async findAllWithPagination(page: number, perPage: number) { + async findAllWithPagination( + page: number, + perPage: number, + titulo_like?: string, + ) { const skip = (page - 1) * perPage; - const usuarios = await this.prismaService.usuario.findMany({ - skip, - take: perPage, - }); - return {usuarios}; + let usuarios = Usuario['']; + if (titulo_like) { + usuarios = await this.prismaService.usuario.findMany({ + skip, + take: perPage, + where: { + OR: [ + { nome: { contains: titulo_like, mode: 'insensitive' } }, + { email: { contains: titulo_like, mode: 'insensitive' } }, + { cpf: { contains: titulo_like , mode: 'insensitive'} }, + ], + }, + }); + } else { + usuarios = await this.prismaService.usuario.findMany({ + skip, + take: perPage, + }); + } + return usuarios; } + async create(createUsuarioDto: CreateUsuarioDto) { - const usuario = await this.findOneByUsuario( - createUsuarioDto.nome, - createUsuarioDto.email, - ) - if (!usuario) { - return await this.prismaService.usuario.create({ - data: createUsuarioDto, - }); + if (true) { + createUsuarioDto.senha = await bcrypt.hash(createUsuarioDto.senha, 10); + const usuario = await this.prismaService.usuario.create({ + data: createUsuarioDto, + }); + usuario.senha = undefined; + return usuario; + } + return { data: { message: 'Cpf ja cadastrado' } }; } - return {data: {message: 'Usuario ja cadastrado'}}; + + async findByEmail(email: string) { + const usuario = await this.prismaService.usuario.findFirst({ + where: { email }, + }); + return usuario; } - async findByEmail(email: string){ - return await this.prismaService.usuario.findFirst({ - where: {email}, + + async changePassword(id: number, changePasswordDto: ChangePasswordDto) { + + const pass = await bcrypt.hash(changePasswordDto.password, 10); + + const usuario = await this.prismaService.usuario.update({ + where: { id }, + data: { + senha: pass, + }, }); + return usuario; } - async findOneByUsuario(nome:string,email:string){ - return await this.prismaService.usuario.findFirst({ - where: {nome,email} + + async findManyByEmail(email: string) { + const usuarios = await this.prismaService.usuario.findMany({ + where: { email }, }); + return usuarios; } - async countAll() { - return await this.prismaService.usuario.count(); + + async countAll(titulo_like: string = '') { + return await this.prismaService.usuario.count({where: { + OR: [ + { nome: { contains: titulo_like, mode: 'insensitive' } }, + { email: { contains: titulo_like, mode: 'insensitive' } }, + { cpf: { contains: titulo_like , mode: 'insensitive'} }, + ], + },}); } async findAll() { @@ -50,13 +97,21 @@ export class UsuariosService { } async update(id: number, updateUsuarioDto: UpdateUsuarioDto) { - return await this.prismaService.usuario.update({ - where: { id }, - data: updateUsuarioDto, - }) + const usuarioExists = await this.findOne(id); + if (usuarioExists) { + return await this.prismaService.usuario.update({ + where: { id }, + data: updateUsuarioDto, + }); + } + return { data: { message: 'Usuário não existe' } }; } async remove(id: number) { - return await this.prismaService.usuario.delete({ where: { id } }); + const usuarioExists = await this.findOne(id); + if (usuarioExists) { + return await this.prismaService.usuario.delete({ where: { id } }); + } + return { data: { message: 'Usuário não existe' } }; } } diff --git a/src/modules/variantes/dto/create-variante.dto.ts b/src/modules/variantes/dto/create-variante.dto.ts new file mode 100644 index 0000000..e5c04eb --- /dev/null +++ b/src/modules/variantes/dto/create-variante.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, isNumber, IsOptional, IsString, Matches, ValidateIf } from 'class-validator'; + +export class CreateVarianteDto { + + + @IsNumber({}, { message: 'A categoria inserida não é válida' }) + idInsumo: number; + + @IsNotEmpty({ message: 'O titulo não pode estar vazio' }) + @IsString({ message: 'O titulo inserido não é válido' }) + variante: string; + +} diff --git a/src/modules/variantes/dto/update-variante.dto.ts b/src/modules/variantes/dto/update-variante.dto.ts new file mode 100644 index 0000000..989efe0 --- /dev/null +++ b/src/modules/variantes/dto/update-variante.dto.ts @@ -0,0 +1,7 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateVarianteDto } from './create-variante.dto'; + +export class UpdateVarianteDto extends PartialType(CreateVarianteDto) { + variante?: string; + idInsumo?: number; +} diff --git a/src/modules/variantes/entities/variante.entity.ts b/src/modules/variantes/entities/variante.entity.ts new file mode 100644 index 0000000..23d0450 --- /dev/null +++ b/src/modules/variantes/entities/variante.entity.ts @@ -0,0 +1,10 @@ +import { Insumo } from 'src/modules/insumos/entities/insumo.entity'; + +export class Variante { + id: number; + variante: string; + idInsumo: number; + insumo?: Insumo; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/modules/variantes/variantes.controller.spec.ts b/src/modules/variantes/variantes.controller.spec.ts new file mode 100644 index 0000000..dfc41ef --- /dev/null +++ b/src/modules/variantes/variantes.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VariantesController } from './variantes.controller'; +import { VariantesService } from './variantes.service'; + +describe('VariantesController', () => { + let controller: VariantesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VariantesController], + providers: [VariantesService], + }).compile(); + + controller = module.get(VariantesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/variantes/variantes.controller.ts b/src/modules/variantes/variantes.controller.ts new file mode 100644 index 0000000..c750faa --- /dev/null +++ b/src/modules/variantes/variantes.controller.ts @@ -0,0 +1,91 @@ +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + Query, + Header, + Res, +} from '@nestjs/common'; +import { VariantesService } from './variantes.service'; +import { CreateVarianteDto } from './dto/create-variante.dto'; +import { UpdateVarianteDto } from './dto/update-variante.dto'; + +@Controller('variantes') +export class VariantesController { + constructor(private readonly variantesService: VariantesService) {} + + @Get('count') + async countAll() { + return await this.variantesService.countAll(); + } + + @Post() + async create(@Body() createVarianteDto: CreateVarianteDto) { + + return await this.variantesService.create(createVarianteDto); + } + + @Get() + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAll( + @Query('page') page: number, + @Query('perPage') perPage: number, + @Query('titulo_like') titulo_like: string, + @Res({ passthrough: true }) res, + ) { + page = page || 1; + perPage = perPage || (await this.countAll()); + const variantes = await this.variantesService.findAll(); + const total = await this.variantesService.countAll(); + res.header('x-total-count', total); + return await variantes; + } + + @Get('insumo/:idInsumo') + @Header('Access-Control-Allow-Origin', '*') + @Header('Access-Control-Expose-Headers', 'X-Total-Count') + async findAllWithId( + @Param('idInsumo') idInsumo: string, + @Query('page') page: number, + @Query('perPage') perPage: number, + @Query('titulo_like') titulo_like: string, + @Res({ passthrough: true }) res, + ) { + + page = page || 1; + perPage = perPage || (await this.countAll()); + const variantes = await this.variantesService.findAllWithId( + +idInsumo, + page, + Number(perPage), + titulo_like, + ); + const total = await this.variantesService.countAllById(+idInsumo,titulo_like); + res.header('x-total-count', total); + return await variantes; + } + + @Get(':id') + async findOne(@Param('id') id: string) { + return await this.variantesService.findOne(+id); + + } + + @Patch(':id') + async update( + @Param('id') id: string, + @Body() updateVarianteDto: UpdateVarianteDto, + ) { + return await this.variantesService.update(+id, updateVarianteDto); + } + + @Delete(':id') + async remove(@Param('id') id: string) { + return await this.variantesService.remove(+id); + } +} diff --git a/src/modules/variantes/variantes.module.ts b/src/modules/variantes/variantes.module.ts new file mode 100644 index 0000000..8092611 --- /dev/null +++ b/src/modules/variantes/variantes.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { VariantesService } from './variantes.service'; +import { VariantesController } from './variantes.controller'; +import { PrismaModule } from 'src/databases/prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [VariantesController], + providers: [VariantesService], +}) +export class VariantesModule {} diff --git a/src/modules/variantes/variantes.service.spec.ts b/src/modules/variantes/variantes.service.spec.ts new file mode 100644 index 0000000..7441273 --- /dev/null +++ b/src/modules/variantes/variantes.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VariantesService } from './variantes.service'; + +describe('VariantesService', () => { + let service: VariantesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [VariantesService], + }).compile(); + + service = module.get(VariantesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/variantes/variantes.service.ts b/src/modules/variantes/variantes.service.ts new file mode 100644 index 0000000..098da06 --- /dev/null +++ b/src/modules/variantes/variantes.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@nestjs/common'; +import { CreateVarianteDto } from './dto/create-variante.dto'; +import { UpdateVarianteDto } from './dto/update-variante.dto'; +import { PrismaService } from 'src/databases/prisma/prisma.service'; +import { Variante } from './entities/variante.entity'; + +@Injectable() +export class VariantesService { + constructor(private readonly prismaService: PrismaService) {} + + async create(createVarianteDto: CreateVarianteDto) { + return await this.prismaService.variante.create({ data: createVarianteDto }); + } + + async findAllWithId( + idInsumo: number, + page: number, + perPage: number, + titulo_like?: string, + ) { + const skip = (page - 1) * perPage; + let variantes = Variante['']; + if (titulo_like) { + variantes = await this.prismaService.variante.findMany({ + skip, + take: perPage, + where: { + idInsumo: idInsumo, + OR: [ + + { variante: { contains: titulo_like } }, + ], + }, + include: { insumo: true }, + }); + } else { + variantes = await this.prismaService.variante.findMany({ + skip, + take: perPage, + where: { idInsumo: idInsumo }, + include: { insumo: true }, + }); + } + return variantes; + } + + async findAll() { + return await this.prismaService.variante.findMany({ + include: { insumo: true }, + }); + } + + async findOne(id: number) { + return await this.prismaService.variante.findFirst({ + where: { id }, + include: { insumo: true }, + }); + } + + async update(id: number, updateVarianteDto: UpdateVarianteDto) { + return await this.prismaService.variante.update({ + where: { id }, + data: updateVarianteDto, + }); + } + + async remove(id: number) { + return await this.prismaService.variante.delete({ where: { id } }); + } + + async countAll() { + return await this.prismaService.variante.count(); + } + async countAllById(idInsumo: number, titulo_like: string = '') { + return await this.prismaService.variante.count({ + where: { idInsumo:idInsumo, + OR: [ + + { variante: { contains: titulo_like, mode: 'insensitive' } }, + ], }, + }); + } +}