HEFESTO v1.0 - Sistema de Controle Orçamentário para Facilities
- Backend NestJS com 12 módulos - Frontend React com dashboard e gestão - Manuais técnico e de negócios (MD + PDF) - Workflow de aprovação com alçadas - RBAC com 6 perfis de acesso
This commit is contained in:
4
backend/.prettierrc
Normal file
4
backend/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
98
backend/README.md
Normal file
98
backend/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
## Compile and run the project
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ npm run start
|
||||
|
||||
# watch mode
|
||||
$ npm run start:dev
|
||||
|
||||
# production mode
|
||||
$ npm run start:prod
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ npm run test
|
||||
|
||||
# e2e tests
|
||||
$ npm run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ npm run test:cov
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||
|
||||
```bash
|
||||
$ npm install -g @nestjs/mau
|
||||
$ mau deploy
|
||||
```
|
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||
|
||||
## Resources
|
||||
|
||||
Check out a few resources that may come in handy when working with NestJS:
|
||||
|
||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
## Stay in touch
|
||||
|
||||
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||
35
backend/eslint.config.mjs
Normal file
35
backend/eslint.config.mjs
Normal file
@@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
sourceType: 'commonjs',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
||||
},
|
||||
},
|
||||
);
|
||||
8
backend/nest-cli.json
Normal file
8
backend/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
11057
backend/package-lock.json
generated
Normal file
11057
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
86
backend/package.json
Normal file
86
backend/package.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.2",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.3",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"typeorm": "^0.3.28",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^30.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
61
backend/src/app.module.ts
Normal file
61
backend/src/app.module.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { UsersModule } from './modules/users/users.module';
|
||||
import { LocaisModule } from './modules/locais/locais.module';
|
||||
import { CentrosCustoModule } from './modules/centros-custo/centros-custo.module';
|
||||
import { CategoriasModule } from './modules/categorias/categorias.module';
|
||||
import { FornecedoresModule } from './modules/fornecedores/fornecedores.module';
|
||||
import { DemandasModule } from './modules/demandas/demandas.module';
|
||||
import { PropostasModule } from './modules/propostas/propostas.module';
|
||||
import { OrcamentoModule } from './modules/orcamento/orcamento.module';
|
||||
import { WorkflowModule } from './modules/workflow/workflow.module';
|
||||
import { DashboardModule } from './modules/dashboard/dashboard.module';
|
||||
import { OrdensServicoModule } from './modules/ordens-servico/ordens-servico.module';
|
||||
import { SeedService } from './database/seeds/seed.service';
|
||||
import { Perfil } from './modules/users/entities/perfil.entity';
|
||||
import { Usuario } from './modules/users/entities/usuario.entity';
|
||||
import { Local } from './modules/locais/entities/local.entity';
|
||||
import { CentroCusto } from './modules/centros-custo/entities/centro-custo.entity';
|
||||
import { Categoria } from './modules/categorias/entities/categoria.entity';
|
||||
import { Fornecedor } from './modules/fornecedores/entities/fornecedor.entity';
|
||||
import { Certidao } from './modules/fornecedores/entities/certidao.entity';
|
||||
import { Demanda } from './modules/demandas/entities/demanda.entity';
|
||||
import { ItemLinha } from './modules/demandas/entities/item-linha.entity';
|
||||
import { Proposta } from './modules/propostas/entities/proposta.entity';
|
||||
import { OrcamentoPlanejado } from './modules/orcamento/entities/orcamento-planejado.entity';
|
||||
import { WorkflowAprovacao } from './modules/workflow/entities/workflow-aprovacao.entity';
|
||||
import { OrdemServico } from './modules/ordens-servico/entities/ordem-servico.entity';
|
||||
import { Avaliacao } from './modules/ordens-servico/entities/avaliacao.entity';
|
||||
import { Alerta } from './modules/dashboard/entities/alerta.entity';
|
||||
import { AuditLog } from './modules/dashboard/entities/audit-log.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'better-sqlite3',
|
||||
database: 'hefesto.db',
|
||||
autoLoadEntities: true,
|
||||
synchronize: true,
|
||||
}),
|
||||
TypeOrmModule.forFeature([
|
||||
Perfil, Usuario, Local, CentroCusto, Categoria, Fornecedor, Certidao,
|
||||
Demanda, ItemLinha, Proposta, OrcamentoPlanejado, WorkflowAprovacao,
|
||||
OrdemServico, Avaliacao, Alerta, AuditLog,
|
||||
]),
|
||||
AuthModule,
|
||||
UsersModule,
|
||||
LocaisModule,
|
||||
CentrosCustoModule,
|
||||
CategoriasModule,
|
||||
FornecedoresModule,
|
||||
DemandasModule,
|
||||
PropostasModule,
|
||||
OrcamentoModule,
|
||||
WorkflowModule,
|
||||
DashboardModule,
|
||||
OrdensServicoModule,
|
||||
],
|
||||
providers: [SeedService],
|
||||
})
|
||||
export class AppModule {}
|
||||
3
backend/src/common/decorators/roles.decorator.ts
Normal file
3
backend/src/common/decorators/roles.decorator.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
export const ROLES_KEY = 'roles';
|
||||
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
||||
19
backend/src/common/guards/jwt-auth.guard.ts
Normal file
19
backend/src/common/guards/jwt-auth.guard.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, ExecutionContext } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (isPublic) return true;
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
||||
19
backend/src/common/guards/roles.guard.ts
Normal file
19
backend/src/common/guards/roles.guard.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ROLES_KEY } from '../decorators/roles.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (!requiredRoles) return true;
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
if (!user) return false;
|
||||
return requiredRoles.includes(user.perfil_nome);
|
||||
}
|
||||
}
|
||||
249
backend/src/database/seeds/seed.service.ts
Normal file
249
backend/src/database/seeds/seed.service.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Perfil } from '../../modules/users/entities/perfil.entity';
|
||||
import { Usuario } from '../../modules/users/entities/usuario.entity';
|
||||
import { Local } from '../../modules/locais/entities/local.entity';
|
||||
import { CentroCusto } from '../../modules/centros-custo/entities/centro-custo.entity';
|
||||
import { Categoria } from '../../modules/categorias/entities/categoria.entity';
|
||||
import { Fornecedor } from '../../modules/fornecedores/entities/fornecedor.entity';
|
||||
import { Certidao } from '../../modules/fornecedores/entities/certidao.entity';
|
||||
import { Demanda } from '../../modules/demandas/entities/demanda.entity';
|
||||
import { ItemLinha } from '../../modules/demandas/entities/item-linha.entity';
|
||||
import { Proposta } from '../../modules/propostas/entities/proposta.entity';
|
||||
import { OrcamentoPlanejado } from '../../modules/orcamento/entities/orcamento-planejado.entity';
|
||||
import { WorkflowAprovacao } from '../../modules/workflow/entities/workflow-aprovacao.entity';
|
||||
import { OrdemServico } from '../../modules/ordens-servico/entities/ordem-servico.entity';
|
||||
import { Alerta } from '../../modules/dashboard/entities/alerta.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SeedService {
|
||||
constructor(
|
||||
@InjectRepository(Perfil) private perfilRepo: Repository<Perfil>,
|
||||
@InjectRepository(Usuario) private userRepo: Repository<Usuario>,
|
||||
@InjectRepository(Local) private localRepo: Repository<Local>,
|
||||
@InjectRepository(CentroCusto) private ccRepo: Repository<CentroCusto>,
|
||||
@InjectRepository(Categoria) private catRepo: Repository<Categoria>,
|
||||
@InjectRepository(Fornecedor) private fornRepo: Repository<Fornecedor>,
|
||||
@InjectRepository(Certidao) private certRepo: Repository<Certidao>,
|
||||
@InjectRepository(Demanda) private demandaRepo: Repository<Demanda>,
|
||||
@InjectRepository(ItemLinha) private itemRepo: Repository<ItemLinha>,
|
||||
@InjectRepository(Proposta) private propRepo: Repository<Proposta>,
|
||||
@InjectRepository(OrcamentoPlanejado) private orcRepo: Repository<OrcamentoPlanejado>,
|
||||
@InjectRepository(WorkflowAprovacao) private wfRepo: Repository<WorkflowAprovacao>,
|
||||
@InjectRepository(OrdemServico) private osRepo: Repository<OrdemServico>,
|
||||
@InjectRepository(Alerta) private alertaRepo: Repository<Alerta>,
|
||||
) {}
|
||||
|
||||
async seed() {
|
||||
const existingPerfis = await this.perfilRepo.count();
|
||||
if (existingPerfis > 0) return;
|
||||
|
||||
console.log('🌱 Seeding database...');
|
||||
const hash = await bcrypt.hash('123456', 10);
|
||||
|
||||
// Perfis
|
||||
const perfis = [
|
||||
{ id: uuid(), nome: 'solicitante', descricao: 'Solicitante de demandas', permissoes: {} },
|
||||
{ id: uuid(), nome: 'gestor_facilities', descricao: 'Gestor de Facilities', permissoes: {} },
|
||||
{ id: uuid(), nome: 'aprovador_financeiro', descricao: 'Aprovador Financeiro', permissoes: {} },
|
||||
{ id: uuid(), nome: 'diretoria', descricao: 'Diretoria', permissoes: {} },
|
||||
{ id: uuid(), nome: 'fornecedor', descricao: 'Fornecedor', permissoes: {} },
|
||||
{ id: uuid(), nome: 'administrador', descricao: 'Administrador do Sistema', permissoes: {} },
|
||||
];
|
||||
await this.perfilRepo.save(perfis);
|
||||
|
||||
// Centros de Custo
|
||||
const ccs = [
|
||||
{ id: uuid(), codigo: 'CC-001', nome: 'Sede Corporativa SP' },
|
||||
{ id: uuid(), codigo: 'CC-002', nome: 'Filial Rio de Janeiro' },
|
||||
{ id: uuid(), codigo: 'CC-003', nome: 'Centro de Distribuição MG' },
|
||||
{ id: uuid(), codigo: 'CC-004', nome: 'Escritório Curitiba' },
|
||||
{ id: uuid(), codigo: 'CC-005', nome: 'Fábrica Campinas' },
|
||||
];
|
||||
await this.ccRepo.save(ccs);
|
||||
|
||||
// Usuários
|
||||
const users = [
|
||||
{ id: uuid(), nome: 'Admin Sistema', email: 'admin@hefesto.com', senha_hash: hash, perfil_id: perfis[5].id },
|
||||
{ id: uuid(), nome: 'Maria Silva', email: 'maria.silva@hefesto.com', senha_hash: hash, perfil_id: perfis[0].id },
|
||||
{ id: uuid(), nome: 'João Santos', email: 'joao.santos@hefesto.com', senha_hash: hash, perfil_id: perfis[1].id },
|
||||
{ id: uuid(), nome: 'Ana Oliveira', email: 'ana.oliveira@hefesto.com', senha_hash: hash, perfil_id: perfis[2].id },
|
||||
{ id: uuid(), nome: 'Carlos Mendes', email: 'carlos.mendes@hefesto.com', senha_hash: hash, perfil_id: perfis[3].id },
|
||||
{ id: uuid(), nome: 'Roberto Lima', email: 'roberto.lima@hefesto.com', senha_hash: hash, perfil_id: perfis[0].id },
|
||||
{ id: uuid(), nome: 'Fernanda Costa', email: 'fernanda.costa@hefesto.com', senha_hash: hash, perfil_id: perfis[1].id },
|
||||
{ id: uuid(), nome: 'Paulo Rodrigues', email: 'paulo.rodrigues@hefesto.com', senha_hash: hash, perfil_id: perfis[4].id },
|
||||
{ id: uuid(), nome: 'Luciana Ferreira', email: 'luciana.ferreira@hefesto.com', senha_hash: hash, perfil_id: perfis[4].id },
|
||||
{ id: uuid(), nome: 'Ricardo Almeida', email: 'ricardo.almeida@hefesto.com', senha_hash: hash, perfil_id: perfis[4].id },
|
||||
];
|
||||
await this.userRepo.save(users);
|
||||
|
||||
// Locais
|
||||
const locais = [
|
||||
{ id: uuid(), nome: 'Torre Norte - SP', endereco: 'Av. Paulista, 1000, São Paulo - SP', centro_custo_id: ccs[0].id, responsavel_id: users[2].id },
|
||||
{ id: uuid(), nome: 'Torre Sul - SP', endereco: 'Av. Paulista, 1002, São Paulo - SP', centro_custo_id: ccs[0].id, responsavel_id: users[2].id },
|
||||
{ id: uuid(), nome: 'Escritório RJ', endereco: 'Rua do Ouvidor, 50, Rio de Janeiro - RJ', centro_custo_id: ccs[1].id, responsavel_id: users[6].id },
|
||||
{ id: uuid(), nome: 'CD Betim', endereco: 'Rod. Fernão Dias, km 492, Betim - MG', centro_custo_id: ccs[2].id, responsavel_id: users[2].id },
|
||||
{ id: uuid(), nome: 'Fábrica Campinas', endereco: 'Distrito Industrial, Campinas - SP', centro_custo_id: ccs[4].id, responsavel_id: users[6].id },
|
||||
];
|
||||
await this.localRepo.save(locais);
|
||||
|
||||
// Categorias
|
||||
const cats = [
|
||||
{ id: uuid(), nome: 'Manutenção Predial', criticidade_padrao: 'media', sla_dias: 15 },
|
||||
{ id: uuid(), nome: 'Climatização e HVAC', criticidade_padrao: 'alta', sla_dias: 7 },
|
||||
{ id: uuid(), nome: 'Limpeza e Conservação', criticidade_padrao: 'baixa', sla_dias: 30 },
|
||||
{ id: uuid(), nome: 'Segurança Patrimonial', criticidade_padrao: 'critica', sla_dias: 3 },
|
||||
{ id: uuid(), nome: 'Elétrica e Iluminação', criticidade_padrao: 'alta', sla_dias: 10 },
|
||||
{ id: uuid(), nome: 'Paisagismo', criticidade_padrao: 'baixa', sla_dias: 45 },
|
||||
{ id: uuid(), nome: 'Dedetização e Controle de Pragas', criticidade_padrao: 'media', sla_dias: 15 },
|
||||
{ id: uuid(), nome: 'Reforma e Adequação', criticidade_padrao: 'media', sla_dias: 60 },
|
||||
];
|
||||
await this.catRepo.save(cats);
|
||||
|
||||
// Fornecedores
|
||||
const forns = [
|
||||
{ id: uuid(), tipo_pessoa: 'PJ', cpf_cnpj: '12.345.678/0001-90', razao_social: 'TechClima Engenharia Ltda', nome_fantasia: 'TechClima', email: 'contato@techclima.com.br', telefone: '(11) 3456-7890', rating: 4.5, usuario_id: users[7].id, categorias_atendidas: [cats[1].id] },
|
||||
{ id: uuid(), tipo_pessoa: 'PJ', cpf_cnpj: '23.456.789/0001-01', razao_social: 'ServiLimp Serviços de Limpeza S/A', nome_fantasia: 'ServiLimp', email: 'comercial@servilimp.com.br', telefone: '(11) 2345-6789', rating: 3.8, usuario_id: users[8].id, categorias_atendidas: [cats[2].id] },
|
||||
{ id: uuid(), tipo_pessoa: 'PJ', cpf_cnpj: '34.567.890/0001-12', razao_social: 'Forte Segurança Empresarial Ltda', nome_fantasia: 'Forte Seg', email: 'propostas@forteseg.com.br', telefone: '(21) 3456-7890', rating: 4.2, usuario_id: users[9].id, categorias_atendidas: [cats[3].id] },
|
||||
{ id: uuid(), tipo_pessoa: 'PJ', cpf_cnpj: '45.678.901/0001-23', razao_social: 'EletroForce Instalações Elétricas', nome_fantasia: 'EletroForce', email: 'orcamento@eletroforce.com.br', telefone: '(11) 4567-8901', rating: 4.0, categorias_atendidas: [cats[4].id] },
|
||||
{ id: uuid(), tipo_pessoa: 'PJ', cpf_cnpj: '56.789.012/0001-34', razao_social: 'Predial Master Engenharia', nome_fantasia: 'Predial Master', email: 'contato@predialmaster.com.br', telefone: '(11) 5678-9012', rating: 4.7, categorias_atendidas: [cats[0].id, cats[7].id] },
|
||||
];
|
||||
await this.fornRepo.save(forns);
|
||||
|
||||
// Certidões
|
||||
const certs = [
|
||||
{ id: uuid(), fornecedor_id: forns[0].id, tipo: 'CND Federal', numero: 'CND-2026-001', data_emissao: '2026-01-10', data_validade: '2026-07-10', status: 'vigente' },
|
||||
{ id: uuid(), fornecedor_id: forns[0].id, tipo: 'CND Estadual', numero: 'CND-2026-002', data_emissao: '2026-01-15', data_validade: '2026-07-15', status: 'vigente' },
|
||||
{ id: uuid(), fornecedor_id: forns[1].id, tipo: 'CND Federal', numero: 'CND-2025-100', data_emissao: '2025-06-01', data_validade: '2025-12-01', status: 'vencida' },
|
||||
{ id: uuid(), fornecedor_id: forns[2].id, tipo: 'CND Federal', numero: 'CND-2026-050', data_emissao: '2026-01-20', data_validade: '2026-07-20', status: 'vigente' },
|
||||
{ id: uuid(), fornecedor_id: forns[2].id, tipo: 'FGTS', numero: 'CRF-2026-003', data_emissao: '2026-01-05', data_validade: '2026-02-05', status: 'vigente' },
|
||||
{ id: uuid(), fornecedor_id: forns[3].id, tipo: 'CND Municipal', numero: 'CND-2026-080', data_emissao: '2025-12-15', data_validade: '2026-06-15', status: 'vigente' },
|
||||
{ id: uuid(), fornecedor_id: forns[4].id, tipo: 'CND Federal', numero: 'CND-2026-200', data_emissao: '2026-02-01', data_validade: '2026-08-01', status: 'vigente' },
|
||||
{ id: uuid(), fornecedor_id: forns[4].id, tipo: 'CND Trabalhista', numero: 'CNDT-2026-010', data_emissao: '2026-01-25', data_validade: '2026-07-25', status: 'vigente' },
|
||||
];
|
||||
await this.certRepo.save(certs);
|
||||
|
||||
// Demandas
|
||||
const demandas = [
|
||||
{ id: uuid(), numero: 1, titulo: 'Manutenção preventiva ar-condicionado Torre Norte', descricao: 'Revisão semestral de todos os 48 splits e 6 centrais de ar', local_id: locais[0].id, centro_custo_id: ccs[0].id, categoria_id: cats[1].id, criticidade: 'alta', status: 'em_cotacao', solicitante_id: users[1].id, gestor_id: users[2].id, data_desejada: '2026-03-15' },
|
||||
{ id: uuid(), numero: 2, titulo: 'Contratação de serviço de limpeza - Escritório RJ', descricao: 'Limpeza diária do escritório RJ - 2 pavimentos, 800m²', local_id: locais[2].id, centro_custo_id: ccs[1].id, categoria_id: cats[2].id, criticidade: 'media', status: 'propostas_recebidas', solicitante_id: users[5].id, gestor_id: users[6].id },
|
||||
{ id: uuid(), numero: 3, titulo: 'Instalação de câmeras de segurança - CD Betim', descricao: 'Instalação de 32 câmeras IP + NVR + cabeamento', local_id: locais[3].id, centro_custo_id: ccs[2].id, categoria_id: cats[3].id, criticidade: 'critica', status: 'em_aprovacao', solicitante_id: users[1].id, gestor_id: users[2].id },
|
||||
{ id: uuid(), numero: 4, titulo: 'Troca de iluminação para LED - Torre Sul', descricao: 'Substituição de 500 lâmpadas fluorescentes por LED', local_id: locais[1].id, centro_custo_id: ccs[0].id, categoria_id: cats[4].id, criticidade: 'media', status: 'aprovada', solicitante_id: users[5].id, gestor_id: users[2].id },
|
||||
{ id: uuid(), numero: 5, titulo: 'Reforma do refeitório - Fábrica Campinas', descricao: 'Reforma completa: piso, pintura, bancadas, instalações', local_id: locais[4].id, centro_custo_id: ccs[4].id, categoria_id: cats[7].id, criticidade: 'media', status: 'em_execucao', solicitante_id: users[1].id, gestor_id: users[6].id },
|
||||
{ id: uuid(), numero: 6, titulo: 'Dedetização trimestral - Todas as unidades', descricao: 'Controle de pragas urbanas em todas as 5 unidades', local_id: locais[0].id, centro_custo_id: ccs[0].id, categoria_id: cats[6].id, criticidade: 'baixa', status: 'aberta', solicitante_id: users[5].id },
|
||||
{ id: uuid(), numero: 7, titulo: 'Reparo no telhado - CD Betim', descricao: 'Vazamento em 3 pontos do galpão principal', local_id: locais[3].id, centro_custo_id: ccs[2].id, categoria_id: cats[0].id, criticidade: 'alta', status: 'em_escopo', solicitante_id: users[1].id, gestor_id: users[2].id },
|
||||
{ id: uuid(), numero: 8, titulo: 'Manutenção do paisagismo - Sede SP', descricao: 'Poda, jardinagem e manutenção das áreas verdes', local_id: locais[0].id, centro_custo_id: ccs[0].id, categoria_id: cats[5].id, criticidade: 'baixa', status: 'rascunho', solicitante_id: users[5].id },
|
||||
{ id: uuid(), numero: 9, titulo: 'Instalação de gerador de emergência - Torre Norte', descricao: 'Gerador diesel 500kVA com QTA', local_id: locais[0].id, centro_custo_id: ccs[0].id, categoria_id: cats[4].id, criticidade: 'critica', status: 'concluida', solicitante_id: users[1].id, gestor_id: users[2].id },
|
||||
{ id: uuid(), numero: 10, titulo: 'Adequação NR-10 - Fábrica Campinas', descricao: 'Adequação de quadros e instalações elétricas à NR-10', local_id: locais[4].id, centro_custo_id: ccs[4].id, categoria_id: cats[4].id, criticidade: 'alta', status: 'cancelada', solicitante_id: users[5].id, gestor_id: users[6].id },
|
||||
];
|
||||
await this.demandaRepo.save(demandas);
|
||||
|
||||
// Itens de Linha
|
||||
const itens = [
|
||||
{ id: uuid(), demanda_id: demandas[0].id, descricao: 'Revisão splits 9000 BTUs (24 un)', tipo: 'mao_de_obra', quantidade: 24, unidade: 'un', ordem: 1 },
|
||||
{ id: uuid(), demanda_id: demandas[0].id, descricao: 'Revisão splits 12000 BTUs (18 un)', tipo: 'mao_de_obra', quantidade: 18, unidade: 'un', ordem: 2 },
|
||||
{ id: uuid(), demanda_id: demandas[0].id, descricao: 'Revisão splits 18000 BTUs (6 un)', tipo: 'mao_de_obra', quantidade: 6, unidade: 'un', ordem: 3 },
|
||||
{ id: uuid(), demanda_id: demandas[0].id, descricao: 'Revisão centrais de ar (6 un)', tipo: 'mao_de_obra', quantidade: 6, unidade: 'un', ordem: 4 },
|
||||
{ id: uuid(), demanda_id: demandas[0].id, descricao: 'Filtros de reposição', tipo: 'material', quantidade: 54, unidade: 'un', ordem: 5 },
|
||||
{ id: uuid(), demanda_id: demandas[2].id, descricao: 'Câmera IP 4MP Hikvision', tipo: 'material', quantidade: 32, unidade: 'un', ordem: 1 },
|
||||
{ id: uuid(), demanda_id: demandas[2].id, descricao: 'NVR 32 canais', tipo: 'equipamento', quantidade: 1, unidade: 'un', ordem: 2 },
|
||||
{ id: uuid(), demanda_id: demandas[2].id, descricao: 'Cabo UTP Cat6 (caixa 305m)', tipo: 'material', quantidade: 4, unidade: 'cx', ordem: 3 },
|
||||
{ id: uuid(), demanda_id: demandas[2].id, descricao: 'Instalação e configuração', tipo: 'mao_de_obra', quantidade: 1, unidade: 'vb', ordem: 4 },
|
||||
{ id: uuid(), demanda_id: demandas[4].id, descricao: 'Demolição e remoção', tipo: 'mao_de_obra', quantidade: 1, unidade: 'vb', ordem: 1 },
|
||||
{ id: uuid(), demanda_id: demandas[4].id, descricao: 'Piso porcelanato 60x60', tipo: 'material', quantidade: 120, unidade: 'm²', ordem: 2 },
|
||||
{ id: uuid(), demanda_id: demandas[4].id, descricao: 'Pintura epóxi paredes', tipo: 'mao_de_obra', quantidade: 280, unidade: 'm²', ordem: 3 },
|
||||
];
|
||||
await this.itemRepo.save(itens);
|
||||
|
||||
// Propostas
|
||||
const propostas = [
|
||||
// Demanda 1 - Ar condicionado
|
||||
{ id: uuid(), demanda_id: demandas[0].id, fornecedor_id: forns[0].id, valor_bruto: 48500, valor_liquido: 42350, impostos: { iss: 2425, inss: 2425, pcc: 1300 }, condicao_pagamento: '30/60 dias', prazo_execucao_dias: 15, status: 'recebida', match_escopo_pct: 95 },
|
||||
{ id: uuid(), demanda_id: demandas[0].id, fornecedor_id: forns[3].id, valor_bruto: 52000, valor_liquido: 45500, impostos: { iss: 2600, inss: 2600, pcc: 1300 }, condicao_pagamento: '30 dias', prazo_execucao_dias: 10, status: 'recebida', match_escopo_pct: 88 },
|
||||
// Demanda 2 - Limpeza
|
||||
{ id: uuid(), demanda_id: demandas[1].id, fornecedor_id: forns[1].id, valor_bruto: 18500, valor_liquido: 16200, impostos: { iss: 925, inss: 925, pcc: 450 }, condicao_pagamento: 'Mensal', prazo_execucao_dias: 365, status: 'analisada', match_escopo_pct: 100 },
|
||||
{ id: uuid(), demanda_id: demandas[1].id, fornecedor_id: forns[4].id, valor_bruto: 21000, valor_liquido: 18400, impostos: { iss: 1050, inss: 1050, pcc: 500 }, condicao_pagamento: 'Mensal', prazo_execucao_dias: 365, status: 'recebida', match_escopo_pct: 92 },
|
||||
// Demanda 3 - Câmeras
|
||||
{ id: uuid(), demanda_id: demandas[2].id, fornecedor_id: forns[2].id, valor_bruto: 145000, valor_liquido: 126800, impostos: { iss: 7250, inss: 7250, pcc: 3700 }, condicao_pagamento: '30/60/90 dias', prazo_execucao_dias: 30, status: 'selecionada', selecionada: true, match_escopo_pct: 97 },
|
||||
{ id: uuid(), demanda_id: demandas[2].id, fornecedor_id: forns[3].id, valor_bruto: 158000, valor_liquido: 138200, impostos: { iss: 7900, inss: 7900, pcc: 4000 }, condicao_pagamento: '50% + 50%', prazo_execucao_dias: 25, status: 'rejeitada', match_escopo_pct: 85 },
|
||||
{ id: uuid(), demanda_id: demandas[2].id, fornecedor_id: forns[4].id, valor_bruto: 139000, valor_liquido: 121600, impostos: { iss: 6950, inss: 6950, pcc: 3500 }, condicao_pagamento: '30/60/90 dias', prazo_execucao_dias: 35, status: 'rejeitada', match_escopo_pct: 90 },
|
||||
// Demanda 4 - LED
|
||||
{ id: uuid(), demanda_id: demandas[3].id, fornecedor_id: forns[3].id, valor_bruto: 75000, valor_liquido: 65600, impostos: { iss: 3750, inss: 3750, pcc: 1900 }, condicao_pagamento: '30/60 dias', prazo_execucao_dias: 20, status: 'selecionada', selecionada: true, match_escopo_pct: 98 },
|
||||
{ id: uuid(), demanda_id: demandas[3].id, fornecedor_id: forns[0].id, valor_bruto: 82000, valor_liquido: 71800, impostos: { iss: 4100, inss: 4100, pcc: 2000 }, condicao_pagamento: '30 dias', prazo_execucao_dias: 25, status: 'rejeitada', match_escopo_pct: 93 },
|
||||
// Demanda 5 - Reforma refeitório
|
||||
{ id: uuid(), demanda_id: demandas[4].id, fornecedor_id: forns[4].id, valor_bruto: 280000, valor_liquido: 245000, impostos: { iss: 14000, inss: 14000, pcc: 7000 }, condicao_pagamento: 'Medição mensal', prazo_execucao_dias: 90, status: 'selecionada', selecionada: true, match_escopo_pct: 100 },
|
||||
{ id: uuid(), demanda_id: demandas[4].id, fornecedor_id: forns[3].id, valor_bruto: 310000, valor_liquido: 271200, impostos: { iss: 15500, inss: 15500, pcc: 7800 }, condicao_pagamento: '30/60/90 dias', prazo_execucao_dias: 75, status: 'rejeitada', match_escopo_pct: 88 },
|
||||
// Demanda 9 - Gerador
|
||||
{ id: uuid(), demanda_id: demandas[8].id, fornecedor_id: forns[3].id, valor_bruto: 320000, valor_liquido: 280000, impostos: { iss: 16000, inss: 16000, pcc: 8000 }, condicao_pagamento: '50% + 50%', prazo_execucao_dias: 45, status: 'selecionada', selecionada: true, match_escopo_pct: 96 },
|
||||
{ id: uuid(), demanda_id: demandas[8].id, fornecedor_id: forns[4].id, valor_bruto: 295000, valor_liquido: 258100, impostos: { iss: 14750, inss: 14750, pcc: 7400 }, condicao_pagamento: '30/60/90 dias', prazo_execucao_dias: 60, status: 'rejeitada', match_escopo_pct: 91 },
|
||||
{ id: uuid(), demanda_id: demandas[8].id, fornecedor_id: forns[0].id, valor_bruto: 345000, valor_liquido: 301800, impostos: { iss: 17250, inss: 17250, pcc: 8700 }, condicao_pagamento: '30 dias', prazo_execucao_dias: 40, status: 'rejeitada', match_escopo_pct: 82 },
|
||||
];
|
||||
await this.propRepo.save(propostas);
|
||||
|
||||
// Workflow
|
||||
const workflows = [
|
||||
{ id: uuid(), demanda_id: demandas[2].id, proposta_id: propostas[4].id, valor_total: 145000, status: 'em_andamento', etapa_atual: 2, etapas: [
|
||||
{ ordem: 1, perfil: 'gestor_facilities', status: 'aprovado', data_acao: '2026-02-05T10:00:00Z', observacao: 'Projeto necessário para segurança', ressalva: false },
|
||||
{ ordem: 2, perfil: 'aprovador_financeiro', status: 'pendente', data_acao: null, observacao: null, ressalva: false },
|
||||
{ ordem: 3, perfil: 'diretoria', status: 'pendente', data_acao: null, observacao: null, ressalva: false },
|
||||
]},
|
||||
{ id: uuid(), demanda_id: demandas[3].id, proposta_id: propostas[7].id, valor_total: 75000, status: 'aprovado', etapa_atual: 3, etapas: [
|
||||
{ ordem: 1, perfil: 'gestor_facilities', status: 'aprovado', data_acao: '2026-01-20T09:00:00Z', observacao: 'OK', ressalva: false },
|
||||
{ ordem: 2, perfil: 'aprovador_financeiro', status: 'aprovado', data_acao: '2026-01-21T14:00:00Z', observacao: 'Dentro do orçamento', ressalva: false },
|
||||
{ ordem: 3, perfil: 'diretoria', status: 'aprovado', data_acao: '2026-01-22T11:00:00Z', observacao: 'Aprovado', ressalva: false },
|
||||
]},
|
||||
{ id: uuid(), demanda_id: demandas[4].id, proposta_id: propostas[9].id, valor_total: 280000, status: 'aprovado', etapa_atual: 3, etapas: [
|
||||
{ ordem: 1, perfil: 'gestor_facilities', status: 'aprovado', data_acao: '2026-01-10T10:00:00Z', observacao: 'Urgente', ressalva: false },
|
||||
{ ordem: 2, perfil: 'aprovador_financeiro', status: 'aprovado_com_ressalva', data_acao: '2026-01-11T16:00:00Z', observacao: 'Verificar possibilidade de parcelamento maior', ressalva: true },
|
||||
{ ordem: 3, perfil: 'diretoria', status: 'aprovado', data_acao: '2026-01-12T09:00:00Z', observacao: 'Aprovado', ressalva: false },
|
||||
]},
|
||||
];
|
||||
await this.wfRepo.save(workflows);
|
||||
|
||||
// Ordens de Serviço
|
||||
const oss = [
|
||||
{ id: uuid(), numero: 1, demanda_id: demandas[3].id, proposta_id: propostas[7].id, fornecedor_id: forns[3].id, valor: 75000, status: 'emitida', data_inicio: null },
|
||||
{ id: uuid(), numero: 2, demanda_id: demandas[4].id, proposta_id: propostas[9].id, fornecedor_id: forns[4].id, valor: 280000, status: 'em_execucao', data_inicio: '2026-01-20' },
|
||||
{ id: uuid(), numero: 3, demanda_id: demandas[8].id, proposta_id: propostas[11].id, fornecedor_id: forns[3].id, valor: 320000, status: 'concluida', data_inicio: '2025-11-01', data_conclusao: '2025-12-15' },
|
||||
];
|
||||
await this.osRepo.save(oss);
|
||||
|
||||
// Orçamento Planejado 2026
|
||||
const orcData = [];
|
||||
for (let mes = 1; mes <= 12; mes++) {
|
||||
for (const cc of ccs) {
|
||||
for (const cat of cats.slice(0, 5)) {
|
||||
orcData.push({
|
||||
id: uuid(),
|
||||
ano: 2026,
|
||||
mes,
|
||||
centro_custo_id: cc.id,
|
||||
categoria_id: cat.id,
|
||||
valor_planejado: Math.round((15000 + Math.random() * 85000) * 100) / 100,
|
||||
valor_comprometido: mes <= 2 ? Math.round(Math.random() * 30000 * 100) / 100 : 0,
|
||||
valor_realizado: mes <= 1 ? Math.round(Math.random() * 20000 * 100) / 100 : 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save in batches
|
||||
for (let i = 0; i < orcData.length; i += 50) {
|
||||
await this.orcRepo.save(orcData.slice(i, i + 50));
|
||||
}
|
||||
|
||||
// Alertas
|
||||
const alertas = [
|
||||
{ id: uuid(), usuario_id: users[3].id, tipo: 'aprovacao_pendente', titulo: 'Aprovação pendente: Câmeras CD Betim', mensagem: 'A demanda #3 aguarda sua aprovação financeira. Valor: R$ 145.000,00', entidade: 'workflow', entidade_id: workflows[0].id },
|
||||
{ id: uuid(), usuario_id: users[2].id, tipo: 'sla_vencendo', titulo: 'SLA próximo: Dedetização', mensagem: 'A demanda #6 está há 10 dias sem andamento', entidade: 'demanda', entidade_id: demandas[5].id },
|
||||
{ id: uuid(), usuario_id: users[0].id, tipo: 'certidao_vencendo', titulo: 'Certidão vencida: ServiLimp', mensagem: 'A CND Federal da ServiLimp está vencida desde 01/12/2025', entidade: 'fornecedor', entidade_id: forns[1].id },
|
||||
{ id: uuid(), usuario_id: users[2].id, tipo: 'estouro_orcamento', titulo: 'Alerta de orçamento: CC Sede SP', mensagem: 'O centro de custo CC-001 está com comprometimento acima de 80% para Climatização em Janeiro', entidade: 'orcamento' },
|
||||
{ id: uuid(), usuario_id: users[6].id, tipo: 'os_atrasada', titulo: 'OS em atraso: Reforma Refeitório', mensagem: 'A OS #2 está em execução há mais de 30 dias', entidade: 'ordem_servico', entidade_id: oss[1].id },
|
||||
];
|
||||
await this.alertaRepo.save(alertas);
|
||||
|
||||
console.log('✅ Seed completed!');
|
||||
}
|
||||
}
|
||||
18
backend/src/main.ts
Normal file
18
backend/src/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { AppModule } from './app.module';
|
||||
import { SeedService } from './database/seeds/seed.service';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.setGlobalPrefix('api');
|
||||
app.enableCors({ origin: '*' });
|
||||
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
||||
|
||||
const seedService = app.get(SeedService);
|
||||
await seedService.seed();
|
||||
|
||||
await app.listen(8080);
|
||||
console.log('HEFESTO API running on http://localhost:8080');
|
||||
}
|
||||
bootstrap();
|
||||
20
backend/src/modules/auth/auth.controller.ts
Normal file
20
backend/src/modules/auth/auth.controller.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Controller, Post, Body, Get, UseGuards, Request, SetMetadata } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@SetMetadata('isPublic', true)
|
||||
@Post('login')
|
||||
async login(@Body() body: { email: string; senha: string }) {
|
||||
return this.authService.login(body.email, body.senha);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('me')
|
||||
async me(@Request() req) {
|
||||
return this.authService.me(req.user.sub);
|
||||
}
|
||||
}
|
||||
24
backend/src/modules/auth/auth.module.ts
Normal file
24
backend/src/modules/auth/auth.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
import { Usuario } from '../users/entities/usuario.entity';
|
||||
import { Perfil } from '../users/entities/perfil.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Usuario, Perfil]),
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: 'hefesto-jwt-secret-2026',
|
||||
signOptions: { expiresIn: '24h' },
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
exports: [AuthService, JwtModule],
|
||||
})
|
||||
export class AuthModule {}
|
||||
63
backend/src/modules/auth/auth.service.ts
Normal file
63
backend/src/modules/auth/auth.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { Usuario } from '../users/entities/usuario.entity';
|
||||
import { Perfil } from '../users/entities/perfil.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
@InjectRepository(Usuario) private userRepo: Repository<Usuario>,
|
||||
@InjectRepository(Perfil) private perfilRepo: Repository<Perfil>,
|
||||
private jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
async login(email: string, senha: string) {
|
||||
const user = await this.userRepo.findOne({
|
||||
where: { email, ativo: true },
|
||||
relations: ['perfil'],
|
||||
});
|
||||
if (!user) throw new UnauthorizedException('Credenciais inválidas');
|
||||
|
||||
const valid = await bcrypt.compare(senha, user.senha_hash);
|
||||
if (!valid) throw new UnauthorizedException('Credenciais inválidas');
|
||||
|
||||
user.ultimo_acesso = new Date();
|
||||
await this.userRepo.save(user);
|
||||
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
email: user.email,
|
||||
nome: user.nome,
|
||||
perfil_id: user.perfil_id,
|
||||
perfil_nome: user.perfil.nome,
|
||||
};
|
||||
|
||||
return {
|
||||
access_token: this.jwtService.sign(payload),
|
||||
user: {
|
||||
id: user.id,
|
||||
nome: user.nome,
|
||||
email: user.email,
|
||||
perfil: user.perfil.nome,
|
||||
perfil_id: user.perfil_id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async me(userId: string) {
|
||||
const user = await this.userRepo.findOne({
|
||||
where: { id: userId },
|
||||
relations: ['perfil'],
|
||||
});
|
||||
return {
|
||||
id: user.id,
|
||||
nome: user.nome,
|
||||
email: user.email,
|
||||
perfil: user.perfil.nome,
|
||||
perfil_id: user.perfil_id,
|
||||
};
|
||||
}
|
||||
}
|
||||
24
backend/src/modules/auth/jwt.strategy.ts
Normal file
24
backend/src/modules/auth/jwt.strategy.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: 'hefesto-jwt-secret-2026',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return {
|
||||
sub: payload.sub,
|
||||
email: payload.email,
|
||||
nome: payload.nome,
|
||||
perfil_id: payload.perfil_id,
|
||||
perfil_nome: payload.perfil_nome,
|
||||
};
|
||||
}
|
||||
}
|
||||
13
backend/src/modules/categorias/categorias.controller.ts
Normal file
13
backend/src/modules/categorias/categorias.controller.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common';
|
||||
import { CategoriasService } from './categorias.service';
|
||||
|
||||
@Controller('categorias')
|
||||
export class CategoriasController {
|
||||
constructor(private svc: CategoriasService) {}
|
||||
|
||||
@Get() findAll() { return this.svc.findAll(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch(':id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
@Delete(':id') remove(@Param('id') id: string) { return this.svc.remove(id); }
|
||||
}
|
||||
13
backend/src/modules/categorias/categorias.module.ts
Normal file
13
backend/src/modules/categorias/categorias.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Categoria } from './entities/categoria.entity';
|
||||
import { CategoriasController } from './categorias.controller';
|
||||
import { CategoriasService } from './categorias.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Categoria])],
|
||||
controllers: [CategoriasController],
|
||||
providers: [CategoriasService],
|
||||
exports: [CategoriasService],
|
||||
})
|
||||
export class CategoriasModule {}
|
||||
14
backend/src/modules/categorias/categorias.service.ts
Normal file
14
backend/src/modules/categorias/categorias.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Categoria } from './entities/categoria.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CategoriasService {
|
||||
constructor(@InjectRepository(Categoria) private repo: Repository<Categoria>) {}
|
||||
findAll() { return this.repo.find({ where: { ativo: true } }); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
create(data: Partial<Categoria>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<Categoria>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
async remove(id: string) { await this.repo.update(id, { ativo: false }); }
|
||||
}
|
||||
31
backend/src/modules/categorias/entities/categoria.entity.ts
Normal file
31
backend/src/modules/categorias/entities/categoria.entity.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('categorias')
|
||||
export class Categoria {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ length: 200 })
|
||||
nome: string;
|
||||
|
||||
@Column({ length: 200, nullable: true })
|
||||
subcategoria: string;
|
||||
|
||||
@Column({ length: 20, nullable: true })
|
||||
criticidade_padrao: string;
|
||||
|
||||
@Column({ default: 30 })
|
||||
sla_dias: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
categoria_pai_id: string;
|
||||
|
||||
@Column({ default: true })
|
||||
ativo: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common';
|
||||
import { CentrosCustoService } from './centros-custo.service';
|
||||
|
||||
@Controller('centros-custo')
|
||||
export class CentrosCustoController {
|
||||
constructor(private svc: CentrosCustoService) {}
|
||||
|
||||
@Get() findAll() { return this.svc.findAll(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch(':id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
@Delete(':id') remove(@Param('id') id: string) { return this.svc.remove(id); }
|
||||
}
|
||||
13
backend/src/modules/centros-custo/centros-custo.module.ts
Normal file
13
backend/src/modules/centros-custo/centros-custo.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CentroCusto } from './entities/centro-custo.entity';
|
||||
import { CentrosCustoController } from './centros-custo.controller';
|
||||
import { CentrosCustoService } from './centros-custo.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([CentroCusto])],
|
||||
controllers: [CentrosCustoController],
|
||||
providers: [CentrosCustoService],
|
||||
exports: [CentrosCustoService],
|
||||
})
|
||||
export class CentrosCustoModule {}
|
||||
14
backend/src/modules/centros-custo/centros-custo.service.ts
Normal file
14
backend/src/modules/centros-custo/centros-custo.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CentroCusto } from './entities/centro-custo.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CentrosCustoService {
|
||||
constructor(@InjectRepository(CentroCusto) private repo: Repository<CentroCusto>) {}
|
||||
findAll() { return this.repo.find({ where: { ativo: true } }); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
create(data: Partial<CentroCusto>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<CentroCusto>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
async remove(id: string) { await this.repo.update(id, { ativo: false }); }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('centros_custo')
|
||||
export class CentroCusto {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ unique: true, length: 20 })
|
||||
codigo: string;
|
||||
|
||||
@Column({ length: 200 })
|
||||
nome: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
responsavel_id: string;
|
||||
|
||||
@Column({ default: true })
|
||||
ativo: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
13
backend/src/modules/dashboard/dashboard.controller.ts
Normal file
13
backend/src/modules/dashboard/dashboard.controller.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Patch, Param, Query } from '@nestjs/common';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
@Controller('dashboard')
|
||||
export class DashboardController {
|
||||
constructor(private svc: DashboardService) {}
|
||||
|
||||
@Get('indicadores') indicadores() { return this.svc.indicadores(); }
|
||||
@Get('demandas-por-status') demandasPorStatus() { return this.svc.demandasPorStatus(); }
|
||||
@Get('consumo-orcamento') consumoOrcamento(@Query('ano') ano: string) { return this.svc.consumoOrcamento(parseInt(ano) || 2026); }
|
||||
@Get('alertas') alertas(@Query('usuario_id') uid: string) { return this.svc.alertas(uid); }
|
||||
@Patch('alertas/:id/ler') marcarLido(@Param('id') id: string) { return this.svc.marcarAlertaLido(id); }
|
||||
}
|
||||
19
backend/src/modules/dashboard/dashboard.module.ts
Normal file
19
backend/src/modules/dashboard/dashboard.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Demanda } from '../demandas/entities/demanda.entity';
|
||||
import { Proposta } from '../propostas/entities/proposta.entity';
|
||||
import { OrdemServico } from '../ordens-servico/entities/ordem-servico.entity';
|
||||
import { OrcamentoPlanejado } from '../orcamento/entities/orcamento-planejado.entity';
|
||||
import { WorkflowAprovacao } from '../workflow/entities/workflow-aprovacao.entity';
|
||||
import { Alerta } from './entities/alerta.entity';
|
||||
import { AuditLog } from './entities/audit-log.entity';
|
||||
import { DashboardController } from './dashboard.controller';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Demanda, Proposta, OrdemServico, OrcamentoPlanejado, WorkflowAprovacao, Alerta, AuditLog])],
|
||||
controllers: [DashboardController],
|
||||
providers: [DashboardService],
|
||||
exports: [DashboardService],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
73
backend/src/modules/dashboard/dashboard.service.ts
Normal file
73
backend/src/modules/dashboard/dashboard.service.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Demanda } from '../demandas/entities/demanda.entity';
|
||||
import { Proposta } from '../propostas/entities/proposta.entity';
|
||||
import { OrdemServico } from '../ordens-servico/entities/ordem-servico.entity';
|
||||
import { OrcamentoPlanejado } from '../orcamento/entities/orcamento-planejado.entity';
|
||||
import { WorkflowAprovacao } from '../workflow/entities/workflow-aprovacao.entity';
|
||||
import { Alerta } from './entities/alerta.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DashboardService {
|
||||
constructor(
|
||||
@InjectRepository(Demanda) private demandaRepo: Repository<Demanda>,
|
||||
@InjectRepository(Proposta) private propostaRepo: Repository<Proposta>,
|
||||
@InjectRepository(OrdemServico) private osRepo: Repository<OrdemServico>,
|
||||
@InjectRepository(OrcamentoPlanejado) private orcRepo: Repository<OrcamentoPlanejado>,
|
||||
@InjectRepository(WorkflowAprovacao) private wfRepo: Repository<WorkflowAprovacao>,
|
||||
@InjectRepository(Alerta) private alertaRepo: Repository<Alerta>,
|
||||
) {}
|
||||
|
||||
async indicadores() {
|
||||
const demandas = await this.demandaRepo.find();
|
||||
const os = await this.osRepo.find();
|
||||
const wf = await this.wfRepo.find();
|
||||
const alertas = await this.alertaRepo.find({ where: { lido: false } });
|
||||
|
||||
return {
|
||||
demandas_abertas: demandas.filter(d => ['aberta', 'em_escopo'].includes(d.status)).length,
|
||||
em_cotacao: demandas.filter(d => d.status === 'em_cotacao').length,
|
||||
pendentes: demandas.filter(d => ['propostas_recebidas', 'em_comparacao'].includes(d.status)).length,
|
||||
em_aprovacao: wf.filter(w => ['pendente', 'em_andamento'].includes(w.status)).length,
|
||||
os_ativas: os.filter(o => ['emitida', 'em_execucao'].includes(o.status)).length,
|
||||
alertas: alertas.length,
|
||||
};
|
||||
}
|
||||
|
||||
async demandasPorStatus() {
|
||||
const demandas = await this.demandaRepo.find();
|
||||
const statusCount: Record<string, number> = {};
|
||||
for (const d of demandas) {
|
||||
statusCount[d.status] = (statusCount[d.status] || 0) + 1;
|
||||
}
|
||||
return Object.entries(statusCount).map(([name, value]) => ({ name, value }));
|
||||
}
|
||||
|
||||
async consumoOrcamento(ano: number) {
|
||||
const items = await this.orcRepo.find({ where: { ano } });
|
||||
const meses = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'];
|
||||
const byMonth: Record<number, any> = {};
|
||||
for (const item of items) {
|
||||
if (!byMonth[item.mes]) byMonth[item.mes] = { name: meses[item.mes - 1], planejado: 0, comprometido: 0, realizado: 0 };
|
||||
byMonth[item.mes].planejado += item.valor_planejado;
|
||||
byMonth[item.mes].comprometido += item.valor_comprometido;
|
||||
byMonth[item.mes].realizado += item.valor_realizado;
|
||||
}
|
||||
return Object.values(byMonth).sort((a: any, b: any) => {
|
||||
const ai = meses.indexOf(a.name);
|
||||
const bi = meses.indexOf(b.name);
|
||||
return ai - bi;
|
||||
});
|
||||
}
|
||||
|
||||
async alertas(usuarioId?: string) {
|
||||
const where: any = { lido: false };
|
||||
if (usuarioId) where.usuario_id = usuarioId;
|
||||
return this.alertaRepo.find({ where, order: { created_at: 'DESC' }, take: 20 });
|
||||
}
|
||||
|
||||
async marcarAlertaLido(id: string) {
|
||||
await this.alertaRepo.update(id, { lido: true });
|
||||
}
|
||||
}
|
||||
31
backend/src/modules/dashboard/entities/alerta.entity.ts
Normal file
31
backend/src/modules/dashboard/entities/alerta.entity.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('alertas')
|
||||
export class Alerta {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
usuario_id: string;
|
||||
|
||||
@Column({ length: 50 })
|
||||
tipo: string;
|
||||
|
||||
@Column({ length: 300 })
|
||||
titulo: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
mensagem: string;
|
||||
|
||||
@Column({ length: 50, nullable: true })
|
||||
entidade: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
entidade_id: string;
|
||||
|
||||
@Column({ default: false })
|
||||
lido: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
34
backend/src/modules/dashboard/entities/audit-log.entity.ts
Normal file
34
backend/src/modules/dashboard/entities/audit-log.entity.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('audit_log')
|
||||
export class AuditLog {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
usuario_id: string;
|
||||
|
||||
@Column({ length: 50 })
|
||||
acao: string;
|
||||
|
||||
@Column({ length: 50 })
|
||||
entidade: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
entidade_id: string;
|
||||
|
||||
@Column({ type: 'simple-json', nullable: true })
|
||||
dados_antes: any;
|
||||
|
||||
@Column({ type: 'simple-json', nullable: true })
|
||||
dados_depois: any;
|
||||
|
||||
@Column({ length: 45, nullable: true })
|
||||
ip: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
user_agent: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
27
backend/src/modules/demandas/demandas.controller.ts
Normal file
27
backend/src/modules/demandas/demandas.controller.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param, Query } from '@nestjs/common';
|
||||
import { DemandasService } from './demandas.service';
|
||||
|
||||
@Controller('demandas')
|
||||
export class DemandasController {
|
||||
constructor(private svc: DemandasService) {}
|
||||
|
||||
@Get() findAll(@Query() query: any) { return this.svc.findAll(query); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch(':id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
|
||||
@Post(':id/publicar') publicar(@Param('id') id: string) { return this.svc.updateStatus(id, 'aberta'); }
|
||||
@Post(':id/cancelar') cancelar(@Param('id') id: string) { return this.svc.updateStatus(id, 'cancelada'); }
|
||||
@Post(':id/enviar-cotacao') enviarCotacao(@Param('id') id: string) { return this.svc.updateStatus(id, 'em_cotacao'); }
|
||||
|
||||
@Get(':id/itens-linha') itens(@Param('id') id: string) { return this.svc.findItens(id); }
|
||||
@Post(':id/itens-linha') createItem(@Param('id') id: string, @Body() body: any) {
|
||||
return this.svc.createItem({ ...body, demanda_id: id });
|
||||
}
|
||||
@Patch(':id/itens-linha/:itemId') updateItem(@Param('itemId') itemId: string, @Body() body: any) {
|
||||
return this.svc.updateItem(itemId, body);
|
||||
}
|
||||
@Delete(':id/itens-linha/:itemId') removeItem(@Param('itemId') itemId: string) {
|
||||
return this.svc.removeItem(itemId);
|
||||
}
|
||||
}
|
||||
14
backend/src/modules/demandas/demandas.module.ts
Normal file
14
backend/src/modules/demandas/demandas.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Demanda } from './entities/demanda.entity';
|
||||
import { ItemLinha } from './entities/item-linha.entity';
|
||||
import { DemandasController } from './demandas.controller';
|
||||
import { DemandasService } from './demandas.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Demanda, ItemLinha])],
|
||||
controllers: [DemandasController],
|
||||
providers: [DemandasService],
|
||||
exports: [DemandasService],
|
||||
})
|
||||
export class DemandasModule {}
|
||||
35
backend/src/modules/demandas/demandas.service.ts
Normal file
35
backend/src/modules/demandas/demandas.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Demanda } from './entities/demanda.entity';
|
||||
import { ItemLinha } from './entities/item-linha.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DemandasService {
|
||||
constructor(
|
||||
@InjectRepository(Demanda) private repo: Repository<Demanda>,
|
||||
@InjectRepository(ItemLinha) private itemRepo: Repository<ItemLinha>,
|
||||
) {}
|
||||
|
||||
findAll(query?: any) {
|
||||
const where: any = {};
|
||||
if (query?.status) where.status = query.status;
|
||||
if (query?.centro_custo_id) where.centro_custo_id = query.centro_custo_id;
|
||||
if (query?.categoria_id) where.categoria_id = query.categoria_id;
|
||||
return this.repo.find({ where, relations: ['itens_linha'], order: { created_at: 'DESC' } });
|
||||
}
|
||||
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id }, relations: ['itens_linha'] }); }
|
||||
create(data: Partial<Demanda>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<Demanda>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
|
||||
async updateStatus(id: string, status: string) {
|
||||
await this.repo.update(id, { status });
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
findItens(demandaId: string) { return this.itemRepo.find({ where: { demanda_id: demandaId }, order: { ordem: 'ASC' } }); }
|
||||
createItem(data: Partial<ItemLinha>) { return this.itemRepo.save(data); }
|
||||
async updateItem(id: string, data: Partial<ItemLinha>) { await this.itemRepo.update(id, data); return this.itemRepo.findOne({ where: { id } }); }
|
||||
async removeItem(id: string) { await this.itemRepo.delete(id); }
|
||||
}
|
||||
53
backend/src/modules/demandas/entities/demanda.entity.ts
Normal file
53
backend/src/modules/demandas/entities/demanda.entity.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
|
||||
import { ItemLinha } from './item-linha.entity';
|
||||
|
||||
@Entity('demandas')
|
||||
export class Demanda {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'integer', unique: true, nullable: true })
|
||||
numero: number;
|
||||
|
||||
@Column({ length: 300 })
|
||||
titulo: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descricao: string;
|
||||
|
||||
@Column()
|
||||
local_id: string;
|
||||
|
||||
@Column()
|
||||
centro_custo_id: string;
|
||||
|
||||
@Column()
|
||||
categoria_id: string;
|
||||
|
||||
@Column({ length: 20 })
|
||||
criticidade: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
data_desejada: string;
|
||||
|
||||
@Column({ length: 30, default: 'rascunho' })
|
||||
status: string;
|
||||
|
||||
@Column()
|
||||
solicitante_id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
gestor_id: string;
|
||||
|
||||
@Column({ type: 'simple-json', default: '[]' })
|
||||
documentos: any;
|
||||
|
||||
@OneToMany(() => ItemLinha, i => i.demanda)
|
||||
itens_linha: ItemLinha[];
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
36
backend/src/modules/demandas/entities/item-linha.entity.ts
Normal file
36
backend/src/modules/demandas/entities/item-linha.entity.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Demanda } from './demanda.entity';
|
||||
|
||||
@Entity('itens_linha')
|
||||
export class ItemLinha {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
demanda_id: string;
|
||||
|
||||
@ManyToOne(() => Demanda, d => d.itens_linha)
|
||||
@JoinColumn({ name: 'demanda_id' })
|
||||
demanda: Demanda;
|
||||
|
||||
@Column({ length: 300 })
|
||||
descricao: string;
|
||||
|
||||
@Column({ length: 50, nullable: true })
|
||||
tipo: string;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
quantidade: number;
|
||||
|
||||
@Column({ length: 30, nullable: true })
|
||||
unidade: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
observacoes: string;
|
||||
|
||||
@Column({ default: 0 })
|
||||
ordem: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
36
backend/src/modules/fornecedores/entities/certidao.entity.ts
Normal file
36
backend/src/modules/fornecedores/entities/certidao.entity.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Fornecedor } from './fornecedor.entity';
|
||||
|
||||
@Entity('certidoes')
|
||||
export class Certidao {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
fornecedor_id: string;
|
||||
|
||||
@ManyToOne(() => Fornecedor, f => f.certidoes)
|
||||
@JoinColumn({ name: 'fornecedor_id' })
|
||||
fornecedor: Fornecedor;
|
||||
|
||||
@Column({ length: 100 })
|
||||
tipo: string;
|
||||
|
||||
@Column({ length: 100, nullable: true })
|
||||
numero: string;
|
||||
|
||||
@Column()
|
||||
data_emissao: string;
|
||||
|
||||
@Column()
|
||||
data_validade: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
arquivo_url: string;
|
||||
|
||||
@Column({ length: 20, default: 'vigente' })
|
||||
status: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
|
||||
import { Certidao } from './certidao.entity';
|
||||
|
||||
@Entity('fornecedores')
|
||||
export class Fornecedor {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ length: 10 })
|
||||
tipo_pessoa: string;
|
||||
|
||||
@Column({ unique: true, length: 18 })
|
||||
cpf_cnpj: string;
|
||||
|
||||
@Column({ length: 300 })
|
||||
razao_social: string;
|
||||
|
||||
@Column({ length: 300, nullable: true })
|
||||
nome_fantasia: string;
|
||||
|
||||
@Column({ length: 255, nullable: true })
|
||||
email: string;
|
||||
|
||||
@Column({ length: 20, nullable: true })
|
||||
telefone: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
endereco: string;
|
||||
|
||||
@Column({ type: 'simple-json', nullable: true })
|
||||
categorias_atendidas: string[];
|
||||
|
||||
@Column({ type: 'float', default: 3.0 })
|
||||
rating: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
usuario_id: string;
|
||||
|
||||
@Column({ default: true })
|
||||
ativo: boolean;
|
||||
|
||||
@OneToMany(() => Certidao, c => c.fornecedor)
|
||||
certidoes: Certidao[];
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
18
backend/src/modules/fornecedores/fornecedores.controller.ts
Normal file
18
backend/src/modules/fornecedores/fornecedores.controller.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common';
|
||||
import { FornecedoresService } from './fornecedores.service';
|
||||
|
||||
@Controller('fornecedores')
|
||||
export class FornecedoresController {
|
||||
constructor(private svc: FornecedoresService) {}
|
||||
|
||||
@Get() findAll() { return this.svc.findAll(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch(':id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
@Delete(':id') remove(@Param('id') id: string) { return this.svc.remove(id); }
|
||||
|
||||
@Get(':id/certidoes') certidoes(@Param('id') id: string) { return this.svc.findCertidoes(id); }
|
||||
@Post(':id/certidoes') createCertidao(@Param('id') id: string, @Body() body: any) {
|
||||
return this.svc.createCertidao({ ...body, fornecedor_id: id });
|
||||
}
|
||||
}
|
||||
14
backend/src/modules/fornecedores/fornecedores.module.ts
Normal file
14
backend/src/modules/fornecedores/fornecedores.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Fornecedor } from './entities/fornecedor.entity';
|
||||
import { Certidao } from './entities/certidao.entity';
|
||||
import { FornecedoresController } from './fornecedores.controller';
|
||||
import { FornecedoresService } from './fornecedores.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Fornecedor, Certidao])],
|
||||
controllers: [FornecedoresController],
|
||||
providers: [FornecedoresService],
|
||||
exports: [FornecedoresService],
|
||||
})
|
||||
export class FornecedoresModule {}
|
||||
22
backend/src/modules/fornecedores/fornecedores.service.ts
Normal file
22
backend/src/modules/fornecedores/fornecedores.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Fornecedor } from './entities/fornecedor.entity';
|
||||
import { Certidao } from './entities/certidao.entity';
|
||||
|
||||
@Injectable()
|
||||
export class FornecedoresService {
|
||||
constructor(
|
||||
@InjectRepository(Fornecedor) private repo: Repository<Fornecedor>,
|
||||
@InjectRepository(Certidao) private certRepo: Repository<Certidao>,
|
||||
) {}
|
||||
|
||||
findAll() { return this.repo.find({ where: { ativo: true }, relations: ['certidoes'] }); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id }, relations: ['certidoes'] }); }
|
||||
create(data: Partial<Fornecedor>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<Fornecedor>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
async remove(id: string) { await this.repo.update(id, { ativo: false }); }
|
||||
|
||||
findCertidoes(fornecedorId: string) { return this.certRepo.find({ where: { fornecedor_id: fornecedorId } }); }
|
||||
createCertidao(data: Partial<Certidao>) { return this.certRepo.save(data); }
|
||||
}
|
||||
28
backend/src/modules/locais/entities/local.entity.ts
Normal file
28
backend/src/modules/locais/entities/local.entity.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
|
||||
@Entity('locais')
|
||||
export class Local {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ length: 200 })
|
||||
nome: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
endereco: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
centro_custo_id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
responsavel_id: string;
|
||||
|
||||
@Column({ default: true })
|
||||
ativo: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
13
backend/src/modules/locais/locais.controller.ts
Normal file
13
backend/src/modules/locais/locais.controller.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common';
|
||||
import { LocaisService } from './locais.service';
|
||||
|
||||
@Controller('locais')
|
||||
export class LocaisController {
|
||||
constructor(private svc: LocaisService) {}
|
||||
|
||||
@Get() findAll() { return this.svc.findAll(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch(':id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
@Delete(':id') remove(@Param('id') id: string) { return this.svc.remove(id); }
|
||||
}
|
||||
13
backend/src/modules/locais/locais.module.ts
Normal file
13
backend/src/modules/locais/locais.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Local } from './entities/local.entity';
|
||||
import { LocaisController } from './locais.controller';
|
||||
import { LocaisService } from './locais.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Local])],
|
||||
controllers: [LocaisController],
|
||||
providers: [LocaisService],
|
||||
exports: [LocaisService],
|
||||
})
|
||||
export class LocaisModule {}
|
||||
14
backend/src/modules/locais/locais.service.ts
Normal file
14
backend/src/modules/locais/locais.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Local } from './entities/local.entity';
|
||||
|
||||
@Injectable()
|
||||
export class LocaisService {
|
||||
constructor(@InjectRepository(Local) private repo: Repository<Local>) {}
|
||||
findAll() { return this.repo.find({ where: { ativo: true } }); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
create(data: Partial<Local>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<Local>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
async remove(id: string) { await this.repo.update(id, { ativo: false }); }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('orcamento_planejado')
|
||||
export class OrcamentoPlanejado {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
ano: number;
|
||||
|
||||
@Column()
|
||||
mes: number;
|
||||
|
||||
@Column()
|
||||
centro_custo_id: string;
|
||||
|
||||
@Column()
|
||||
categoria_id: string;
|
||||
|
||||
@Column({ type: 'float' })
|
||||
valor_planejado: number;
|
||||
|
||||
@Column({ type: 'float', default: 0 })
|
||||
valor_comprometido: number;
|
||||
|
||||
@Column({ type: 'float', default: 0 })
|
||||
valor_realizado: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
13
backend/src/modules/orcamento/orcamento.controller.ts
Normal file
13
backend/src/modules/orcamento/orcamento.controller.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Post, Patch, Body, Param, Query } from '@nestjs/common';
|
||||
import { OrcamentoService } from './orcamento.service';
|
||||
|
||||
@Controller('orcamento')
|
||||
export class OrcamentoController {
|
||||
constructor(private svc: OrcamentoService) {}
|
||||
|
||||
@Get() findAll(@Query() query: any) { return this.svc.findAll(query); }
|
||||
@Get('resumo') resumo(@Query('ano') ano: string) { return this.svc.resumo(parseInt(ano) || 2026); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch(':id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
}
|
||||
13
backend/src/modules/orcamento/orcamento.module.ts
Normal file
13
backend/src/modules/orcamento/orcamento.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { OrcamentoPlanejado } from './entities/orcamento-planejado.entity';
|
||||
import { OrcamentoController } from './orcamento.controller';
|
||||
import { OrcamentoService } from './orcamento.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([OrcamentoPlanejado])],
|
||||
controllers: [OrcamentoController],
|
||||
providers: [OrcamentoService],
|
||||
exports: [OrcamentoService],
|
||||
})
|
||||
export class OrcamentoModule {}
|
||||
33
backend/src/modules/orcamento/orcamento.service.ts
Normal file
33
backend/src/modules/orcamento/orcamento.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { OrcamentoPlanejado } from './entities/orcamento-planejado.entity';
|
||||
|
||||
@Injectable()
|
||||
export class OrcamentoService {
|
||||
constructor(@InjectRepository(OrcamentoPlanejado) private repo: Repository<OrcamentoPlanejado>) {}
|
||||
|
||||
findAll(query?: any) {
|
||||
const where: any = {};
|
||||
if (query?.ano) where.ano = query.ano;
|
||||
if (query?.mes) where.mes = query.mes;
|
||||
if (query?.centro_custo_id) where.centro_custo_id = query.centro_custo_id;
|
||||
return this.repo.find({ where });
|
||||
}
|
||||
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
create(data: Partial<OrcamentoPlanejado>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<OrcamentoPlanejado>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
|
||||
async resumo(ano: number) {
|
||||
const items = await this.repo.find({ where: { ano } });
|
||||
const byMonth: any = {};
|
||||
for (const item of items) {
|
||||
if (!byMonth[item.mes]) byMonth[item.mes] = { mes: item.mes, planejado: 0, comprometido: 0, realizado: 0 };
|
||||
byMonth[item.mes].planejado += item.valor_planejado;
|
||||
byMonth[item.mes].comprometido += item.valor_comprometido;
|
||||
byMonth[item.mes].realizado += item.valor_realizado;
|
||||
}
|
||||
return Object.values(byMonth).sort((a: any, b: any) => a.mes - b.mes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('avaliacoes')
|
||||
export class Avaliacao {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
ordem_servico_id: string;
|
||||
|
||||
@Column()
|
||||
fornecedor_id: string;
|
||||
|
||||
@Column()
|
||||
avaliador_id: string;
|
||||
|
||||
@Column({ type: 'float' })
|
||||
nota: number;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
comentario: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('ordens_servico')
|
||||
export class OrdemServico {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'integer', unique: true, nullable: true })
|
||||
numero: number;
|
||||
|
||||
@Column()
|
||||
demanda_id: string;
|
||||
|
||||
@Column()
|
||||
proposta_id: string;
|
||||
|
||||
@Column()
|
||||
fornecedor_id: string;
|
||||
|
||||
@Column({ type: 'float' })
|
||||
valor: number;
|
||||
|
||||
@Column({ length: 30, default: 'emitida' })
|
||||
status: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
data_inicio: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
data_conclusao: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
observacoes: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
|
||||
import { OrdensServicoService } from './ordens-servico.service';
|
||||
|
||||
@Controller('ordens-servico')
|
||||
export class OrdensServicoController {
|
||||
constructor(private svc: OrdensServicoService) {}
|
||||
|
||||
@Get() findAll() { return this.svc.findAll(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Post(':id/iniciar') iniciar(@Param('id') id: string) { return this.svc.iniciar(id); }
|
||||
@Post(':id/concluir') concluir(@Param('id') id: string) { return this.svc.concluir(id); }
|
||||
@Post(':id/cancelar') cancelar(@Param('id') id: string) { return this.svc.cancelar(id); }
|
||||
@Post(':id/avaliacao') avaliacao(@Param('id') id: string, @Body() body: any) {
|
||||
return this.svc.createAvaliacao({ ...body, ordem_servico_id: id });
|
||||
}
|
||||
}
|
||||
14
backend/src/modules/ordens-servico/ordens-servico.module.ts
Normal file
14
backend/src/modules/ordens-servico/ordens-servico.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { OrdemServico } from './entities/ordem-servico.entity';
|
||||
import { Avaliacao } from './entities/avaliacao.entity';
|
||||
import { OrdensServicoController } from './ordens-servico.controller';
|
||||
import { OrdensServicoService } from './ordens-servico.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([OrdemServico, Avaliacao])],
|
||||
controllers: [OrdensServicoController],
|
||||
providers: [OrdensServicoService],
|
||||
exports: [OrdensServicoService],
|
||||
})
|
||||
export class OrdensServicoModule {}
|
||||
23
backend/src/modules/ordens-servico/ordens-servico.service.ts
Normal file
23
backend/src/modules/ordens-servico/ordens-servico.service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { OrdemServico } from './entities/ordem-servico.entity';
|
||||
import { Avaliacao } from './entities/avaliacao.entity';
|
||||
|
||||
@Injectable()
|
||||
export class OrdensServicoService {
|
||||
constructor(
|
||||
@InjectRepository(OrdemServico) private repo: Repository<OrdemServico>,
|
||||
@InjectRepository(Avaliacao) private avalRepo: Repository<Avaliacao>,
|
||||
) {}
|
||||
|
||||
findAll() { return this.repo.find(); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
create(data: Partial<OrdemServico>) { return this.repo.save(data); }
|
||||
|
||||
async iniciar(id: string) { await this.repo.update(id, { status: 'em_execucao', data_inicio: new Date().toISOString().split('T')[0] }); return this.findOne(id); }
|
||||
async concluir(id: string) { await this.repo.update(id, { status: 'concluida', data_conclusao: new Date().toISOString().split('T')[0] }); return this.findOne(id); }
|
||||
async cancelar(id: string) { await this.repo.update(id, { status: 'cancelada' }); return this.findOne(id); }
|
||||
|
||||
createAvaliacao(data: Partial<Avaliacao>) { return this.avalRepo.save(data); }
|
||||
}
|
||||
52
backend/src/modules/propostas/entities/proposta.entity.ts
Normal file
52
backend/src/modules/propostas/entities/proposta.entity.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('propostas')
|
||||
export class Proposta {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
demanda_id: string;
|
||||
|
||||
@Column()
|
||||
fornecedor_id: string;
|
||||
|
||||
@Column({ default: 1 })
|
||||
versao_atual: number;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
valor_bruto: number;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
valor_liquido: number;
|
||||
|
||||
@Column({ type: 'simple-json', default: '{}' })
|
||||
impostos: any;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
condicao_pagamento: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
prazo_execucao_dias: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
data_entrega_estimada: string;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
match_escopo_pct: number;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
confiabilidade_ocr: number;
|
||||
|
||||
@Column({ default: false })
|
||||
selecionada: boolean;
|
||||
|
||||
@Column({ length: 20, default: 'recebida' })
|
||||
status: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
18
backend/src/modules/propostas/propostas.controller.ts
Normal file
18
backend/src/modules/propostas/propostas.controller.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get, Post, Patch, Body, Param } from '@nestjs/common';
|
||||
import { PropostasService } from './propostas.service';
|
||||
|
||||
@Controller()
|
||||
export class PropostasController {
|
||||
constructor(private svc: PropostasService) {}
|
||||
|
||||
@Get('propostas') findAll() { return this.svc.findAll(); }
|
||||
@Get('propostas/:id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post('propostas') create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Patch('propostas/:id') update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
@Post('propostas/:id/selecionar') selecionar(@Param('id') id: string) { return this.svc.selecionar(id); }
|
||||
|
||||
@Get('demandas/:demandaId/propostas') byDemanda(@Param('demandaId') id: string) { return this.svc.findByDemanda(id); }
|
||||
@Post('demandas/:demandaId/propostas') createForDemanda(@Param('demandaId') demandaId: string, @Body() body: any) {
|
||||
return this.svc.create({ ...body, demanda_id: demandaId });
|
||||
}
|
||||
}
|
||||
13
backend/src/modules/propostas/propostas.module.ts
Normal file
13
backend/src/modules/propostas/propostas.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Proposta } from './entities/proposta.entity';
|
||||
import { PropostasController } from './propostas.controller';
|
||||
import { PropostasService } from './propostas.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Proposta])],
|
||||
controllers: [PropostasController],
|
||||
providers: [PropostasService],
|
||||
exports: [PropostasService],
|
||||
})
|
||||
export class PropostasModule {}
|
||||
23
backend/src/modules/propostas/propostas.service.ts
Normal file
23
backend/src/modules/propostas/propostas.service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Proposta } from './entities/proposta.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PropostasService {
|
||||
constructor(@InjectRepository(Proposta) private repo: Repository<Proposta>) {}
|
||||
|
||||
findByDemanda(demandaId: string) { return this.repo.find({ where: { demanda_id: demandaId } }); }
|
||||
findAll() { return this.repo.find(); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
create(data: Partial<Proposta>) { return this.repo.save(data); }
|
||||
async update(id: string, data: Partial<Proposta>) { await this.repo.update(id, data); return this.findOne(id); }
|
||||
|
||||
async selecionar(id: string) {
|
||||
const prop = await this.findOne(id);
|
||||
// Deselect others for same demanda
|
||||
await this.repo.update({ demanda_id: prop.demanda_id }, { selecionada: false });
|
||||
await this.repo.update(id, { selecionada: true, status: 'selecionada' });
|
||||
return this.findOne(id);
|
||||
}
|
||||
}
|
||||
26
backend/src/modules/users/entities/perfil.entity.ts
Normal file
26
backend/src/modules/users/entities/perfil.entity.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
|
||||
import { Usuario } from './usuario.entity';
|
||||
|
||||
@Entity('perfis')
|
||||
export class Perfil {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ unique: true, length: 50 })
|
||||
nome: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descricao: string;
|
||||
|
||||
@Column({ type: 'simple-json', default: '{}' })
|
||||
permissoes: any;
|
||||
|
||||
@OneToMany(() => Usuario, u => u.perfil)
|
||||
usuarios: Usuario[];
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
36
backend/src/modules/users/entities/usuario.entity.ts
Normal file
36
backend/src/modules/users/entities/usuario.entity.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Perfil } from './perfil.entity';
|
||||
|
||||
@Entity('usuarios')
|
||||
export class Usuario {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ length: 200 })
|
||||
nome: string;
|
||||
|
||||
@Column({ unique: true, length: 255 })
|
||||
email: string;
|
||||
|
||||
@Column({ length: 255 })
|
||||
senha_hash: string;
|
||||
|
||||
@Column()
|
||||
perfil_id: string;
|
||||
|
||||
@ManyToOne(() => Perfil, p => p.usuarios)
|
||||
@JoinColumn({ name: 'perfil_id' })
|
||||
perfil: Perfil;
|
||||
|
||||
@Column({ default: true })
|
||||
ativo: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
ultimo_acesso: Date;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
25
backend/src/modules/users/users.controller.ts
Normal file
25
backend/src/modules/users/users.controller.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Controller()
|
||||
export class UsersController {
|
||||
constructor(private svc: UsersService) {}
|
||||
|
||||
@Get('users')
|
||||
findAll() { return this.svc.findAll(); }
|
||||
|
||||
@Get('users/:id')
|
||||
findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
|
||||
@Post('users')
|
||||
create(@Body() body: any) { return this.svc.create(body); }
|
||||
|
||||
@Patch('users/:id')
|
||||
update(@Param('id') id: string, @Body() body: any) { return this.svc.update(id, body); }
|
||||
|
||||
@Delete('users/:id')
|
||||
remove(@Param('id') id: string) { return this.svc.remove(id); }
|
||||
|
||||
@Get('perfis')
|
||||
perfis() { return this.svc.findAllPerfis(); }
|
||||
}
|
||||
14
backend/src/modules/users/users.module.ts
Normal file
14
backend/src/modules/users/users.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Usuario } from './entities/usuario.entity';
|
||||
import { Perfil } from './entities/perfil.entity';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Usuario, Perfil])],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
20
backend/src/modules/users/users.service.ts
Normal file
20
backend/src/modules/users/users.service.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Usuario } from './entities/usuario.entity';
|
||||
import { Perfil } from './entities/perfil.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
@InjectRepository(Usuario) private userRepo: Repository<Usuario>,
|
||||
@InjectRepository(Perfil) private perfilRepo: Repository<Perfil>,
|
||||
) {}
|
||||
|
||||
findAll() { return this.userRepo.find({ relations: ['perfil'], where: { ativo: true } }); }
|
||||
findOne(id: string) { return this.userRepo.findOne({ where: { id }, relations: ['perfil'] }); }
|
||||
create(data: Partial<Usuario>) { return this.userRepo.save(data); }
|
||||
async update(id: string, data: Partial<Usuario>) { await this.userRepo.update(id, data); return this.findOne(id); }
|
||||
async remove(id: string) { await this.userRepo.update(id, { ativo: false }); }
|
||||
findAllPerfis() { return this.perfilRepo.find(); }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('workflow_aprovacao')
|
||||
export class WorkflowAprovacao {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
demanda_id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
proposta_id: string;
|
||||
|
||||
@Column({ type: 'float' })
|
||||
valor_total: number;
|
||||
|
||||
@Column({ length: 30, default: 'pendente' })
|
||||
status: string;
|
||||
|
||||
@Column({ default: 1 })
|
||||
etapa_atual: number;
|
||||
|
||||
@Column({ type: 'simple-json', default: '[]' })
|
||||
etapas: any;
|
||||
|
||||
@Column({ default: false })
|
||||
emergencial: boolean;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
justificativa_emergencial: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
14
backend/src/modules/workflow/workflow.controller.ts
Normal file
14
backend/src/modules/workflow/workflow.controller.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
|
||||
import { WorkflowService } from './workflow.service';
|
||||
|
||||
@Controller('workflow')
|
||||
export class WorkflowController {
|
||||
constructor(private svc: WorkflowService) {}
|
||||
|
||||
@Get() findAll() { return this.svc.findAll(); }
|
||||
@Get('pendentes') pendentes() { return this.svc.findPendentes(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.svc.findOne(id); }
|
||||
@Post() create(@Body() body: any) { return this.svc.create(body); }
|
||||
@Post(':id/aprovar') aprovar(@Param('id') id: string, @Body() body: any) { return this.svc.aprovar(id, body?.observacao); }
|
||||
@Post(':id/reprovar') reprovar(@Param('id') id: string, @Body() body: any) { return this.svc.reprovar(id, body?.observacao); }
|
||||
}
|
||||
13
backend/src/modules/workflow/workflow.module.ts
Normal file
13
backend/src/modules/workflow/workflow.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { WorkflowAprovacao } from './entities/workflow-aprovacao.entity';
|
||||
import { WorkflowController } from './workflow.controller';
|
||||
import { WorkflowService } from './workflow.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([WorkflowAprovacao])],
|
||||
controllers: [WorkflowController],
|
||||
providers: [WorkflowService],
|
||||
exports: [WorkflowService],
|
||||
})
|
||||
export class WorkflowModule {}
|
||||
51
backend/src/modules/workflow/workflow.service.ts
Normal file
51
backend/src/modules/workflow/workflow.service.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { WorkflowAprovacao } from './entities/workflow-aprovacao.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowService {
|
||||
constructor(@InjectRepository(WorkflowAprovacao) private repo: Repository<WorkflowAprovacao>) {}
|
||||
|
||||
findAll() { return this.repo.find(); }
|
||||
findOne(id: string) { return this.repo.findOne({ where: { id } }); }
|
||||
findByDemanda(demandaId: string) { return this.repo.findOne({ where: { demanda_id: demandaId } }); }
|
||||
create(data: Partial<WorkflowAprovacao>) { return this.repo.save(data); }
|
||||
|
||||
async aprovar(id: string, observacao?: string) {
|
||||
const wf = await this.repo.findOneOrFail({ where: { id } });
|
||||
const etapas = wf.etapas as any[];
|
||||
const current = etapas[wf.etapa_atual - 1];
|
||||
if (current) {
|
||||
current.status = 'aprovado';
|
||||
current.data_acao = new Date().toISOString();
|
||||
current.observacao = observacao || 'Aprovado';
|
||||
}
|
||||
if (wf.etapa_atual >= etapas.length) {
|
||||
wf.status = 'aprovado';
|
||||
} else {
|
||||
wf.etapa_atual++;
|
||||
wf.status = 'em_andamento';
|
||||
}
|
||||
wf.etapas = etapas;
|
||||
return this.repo.save(wf);
|
||||
}
|
||||
|
||||
async reprovar(id: string, observacao: string) {
|
||||
const wf = await this.repo.findOneOrFail({ where: { id } });
|
||||
const etapas = wf.etapas as any[];
|
||||
const current = etapas[wf.etapa_atual - 1];
|
||||
if (current) {
|
||||
current.status = 'reprovado';
|
||||
current.data_acao = new Date().toISOString();
|
||||
current.observacao = observacao;
|
||||
}
|
||||
wf.status = 'reprovado';
|
||||
wf.etapas = etapas;
|
||||
return this.repo.save(wf);
|
||||
}
|
||||
|
||||
findPendentes() {
|
||||
return this.repo.find({ where: { status: 'pendente' } });
|
||||
}
|
||||
}
|
||||
25
backend/test/app.e2e-spec.ts
Normal file
25
backend/test/app.e2e-spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
import { App } from 'supertest/types';
|
||||
import { AppModule } from './../src/app.module';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication<App>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
});
|
||||
});
|
||||
9
backend/test/jest-e2e.json
Normal file
9
backend/test/jest-e2e.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
4
backend/tsconfig.build.json
Normal file
4
backend/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
25
backend/tsconfig.json
Normal file
25
backend/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolvePackageJsonExports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user