Sistema ERP basico multi-tenant desenvolvido com ASP.NET MVC 5 e Entity Framework Core 3.1 utilizando PostgreSQL como banco de dados.
Este projeto de portfolio demonstra uma base SaaS multi-tenant pronta para evoluir, com modelagem profissional para organizations (tenants), usuarios globais, memberships, contas de autenticacao e sessoes.
- Arquitetura multi-tenant baseada em organizations/memberships
- Usuarios globais com roles, metadata em JSONB e suporte a 2FA
- Contas OAuth/credenciais, tokens e sessoes com IP/user-agent
- Docker Compose com PostgreSQL e PgAdmin
- Base pronta para modulos de clientes, produtos e vendas
- Backend: ASP.NET MVC 5 (.NET Framework 4.7.2)
- ORM: Entity Framework Core 3.1.32
- Banco: PostgreSQL 15 + extensoes
uuid-osspecitext - Provider: Npgsql 4.1.9
- Autenticação: OWIN Cookie Authentication + BCrypt.NET
- Email: MailKit + MailHog (desenvolvimento)
- Storage: MinIO para armazenamento de arquivos (fotos de perfil)
- Containerizacao: Docker & Docker Compose
- UI: Bootstrap 5.3.3 + Bootstrap Icons
BasicERP/
├── WebApplicationBasic/ # Projeto web ASP.NET MVC
│ ├── Controllers/ # Controllers MVC (Base, Auth, Home, Account)
│ ├── Views/ # Views Razor
│ │ ├── Auth/ # Telas de autenticação
│ │ ├── Account/ # Telas de gerenciamento de conta
│ │ └── Shared/ # Layout e parciais
│ ├── Services/ # Serviços de autenticação, email, sessão, storage
│ ├── Filters/ # Atributos de autorização customizados
│ ├── Models/ViewModels/ # ViewModels para formulários
│ ├── Data/ # SeedData para testes
│ ├── App_Start/ # Configuracoes (DI, Routes, OWIN)
│ └── Web.config # Configuracoes do app
├── EntityFrameworkProject/ # Projeto de dados
│ ├── Models/ # Entidades (organization, user, etc.)
│ ├── Data/ # DbContext e factory
│ └── app.config # Connection strings
├── docker-compose.yml # PostgreSQL + PgAdmin + MailHog + Redis + RabbitMQ + MinIO
├── AUTHENTICATION_TEST.md # Instruções detalhadas de teste
└── README.md
- Visual Studio 2019 ou superior
- .NET Framework 4.7.2
- Docker Desktop
- Git
git clone https://github.com/psielta/basicerp.git
cd basicerpdocker-compose up -dServicos disponiveis:
- PostgreSQL na porta
5432- Database:
basic_db - Usuario:
adm - Senha:
156879
- Database:
- PgAdmin na porta
5050- Email:
admin@basicerp.com - Senha:
admin
- Email:
- MailHog na porta
8025(Web UI) e1025(SMTP)- Interface: http://localhost:8025
- Redis na porta
6379 - RabbitMQ na porta
15672(Management) e5672(AMQP)- Interface: http://localhost:15672
- Usuario/Senha: guest/guest
- MinIO na porta
9000(API) e9001(Console)- Console: http://localhost:9001
- API: http://localhost:9000
- Usuario/Senha: minioadmin/minioadmin
No Visual Studio abra WebApplicationBasic.sln e execute:
Tools > NuGet Package Manager > Package Manager Console
Update-Package -Reinstall
No Package Manager Console:
- Default project:
EntityFrameworkProject - Startup project:
WebApplicationBasic
Update-DatabaseIsso cria as tabelas:
organization"user"membershipsaccountsession
Pressione F5 no Visual Studio ou clique em Start. A aplicacao roda em https://localhost:44318/.
-
Admin User
- Email:
admin@example.com - Senha:
admin123 - Organização: Empresa Exemplo (Owner)
- Email:
-
João Silva
- Email:
joao@example.com - Senha:
senha123 - Organização: Empresa Exemplo (Member)
- Email:
-
Maria Santos (Múltiplas organizações)
- Email:
maria@example.com - Senha:
maria123 - Organizações: Empresa Exemplo (Admin), Startup Tech (Owner)
- Email:
-
Login com Senha: Digite o email → Escolha a organização (se aplicável) → Escolha "Login com Senha" → Digite a senha
-
Login com OTP: Digite o email → Escolha a organização → Escolha "Login por Código (OTP)" → Verifique o email no MailHog (http://localhost:8025) → Digite o código de 6 dígitos
Para instruções mais detalhadas, consulte o arquivo AUTHENTICATION_TEST.md.
idUUID comuuid_generate_v4()name,slug(citext unico),logometadataJSONB para dados dinamicoscreated_at/updated_atcomnow()
id,name,email(citext unico)email_verified,role(defaultuser),imagemetadataJSONB,two_factor_enabledcreated_at,updated_at
organization_id,user_id,role,team_idcreated_at,updated_at- Constraints: FK cascata, indice unico
(organization_id, user_id)e indices auxiliares por FK
provider_id,account_id(par unico)user_id(FK), tokens de acesso/refresh/id + expiracoesscope,password(para credenciais locais)reset_token,reset_token_expires(recuperação de senha)last_password_change(auditoria)created_at,updated_at
tokenunico,expires_at,created_at,updated_atip_address(inet) euser_agentuser_id(obrigatorio) eactive_organization_id(nullable,ON DELETE SET NULL)
ProductTemplate (SPU - Standard Product Unit):
- Produto "pai" com informações gerais
name,slug,brand,description- Flags:
is_service,is_rental,has_delivery - Fiscais:
ncm,nbs,warranty_months - Relacionamentos: variantes, categorias, atributos descritivos
ProductVariant (SKU - Stock Keeping Unit):
- Variação específica do produto
skuúnico por organização- Dimensões:
weight,height,width,length cost,barcode,is_active- Relacionamentos: atributos de variação
ProductAttribute:
- Define tipos de atributos (Cor, Tamanho, Material, etc.)
is_variant:truepara atributos de variação,falsepara descritivos
Category:
- Hierarquia de categorias com
parent_id pathpara navegação breadcrumb
docker-compose up -d # Subir containers
docker-compose down # Parar containers
docker-compose down -v # Remover containers e volumes
docker-compose down -v # Parar, remover containers E volumes
docker logs basicerp-postgres
docker exec -it basicerp-postgres psql -U adm -d basic_dbAdd-Migration NomeDaMigration
Update-Database
Update-Database -Migration NomeAnterior
Get-Migration-- Listar tabelas
\dt
-- Ver estrutura
\d organization
\d "user"
\d memberships
-- Consultar dados
SELECT * FROM organization;
SELECT * FROM "user";
SELECT * FROM memberships;
SELECT * FROM account;
SELECT * FROM session;- Configuracao EF Core + PostgreSQL
- Docker Compose com PostgreSQL, PgAdmin, MailHog, Redis e RabbitMQ
- Modelo de dados multi-tenant (organization, user, memberships, account, session)
- Injecao de dependencia do DbContext e Serviços
- Interface basica exibindo contagens do banco
- Sistema de Autenticacao Completo
-
Login com email e senha
-
Login com OTP (One-Time Password) via email
-
Suporte a múltiplas organizações
-
Hash de senhas com BCrypt (work factor 12)
-
Sessões gerenciadas no banco de dados
-
Cookies de autenticação OWIN criptografados
-
Proteção de rotas com atributos customizados
-
BaseController com contexto do usuário
-
Seed data para testes
-
Recuperação de Senha
- Esqueceu a senha com envio de email
- Token seguro de reset (24 horas)
- Página de redefinição de senha
- Email de confirmação após alteração
-
Gerenciamento de Conta ("Minha Conta")
- Perfil do usuário com informações completas
- Edição de perfil (nome, email, foto)
- Alteração de senha com validação
- Gerenciamento de sessões ativas
- Visualização e troca de organizações
- Indicador de força de senha
- Preview em tempo real na edição
-
Upload de Fotos de Perfil
- Integração com MinIO para storage
- Interface IStorageService implementada
- Upload de arquivos na edição de perfil
- Validação de tipos de arquivo (jpg, png)
- Redimensionamento e otimização de imagens (max 800x800, 85% qualidade)
-
Sistema de Produtos Completo (SPU/SKU)
- Cadastro de produtos simples e com variações
- Wizard de criação com 5 passos
- Wizard de edição completo
- Categorias hierárquicas
- Sistema de atributos (variação + descritivos)
- Geração automática de variantes (produto cartesiano)
- Gerenciamento individual de variantes
- Soft deletes e auditoria completa
-
Dashboard Executivo
- Cards de estatísticas por organização
- Gráficos Chart.js (produtos por tipo, status de variantes)
- Lista de produtos recentes
- Ações rápidas
-
-
Gestão de Usuários
- CRUD completo de usuários
- Definição de roles
- Associação usuário-organization via memberships
-
Camada de Clientes
- CRUD de clientes por organization
- Validação de CPF/CNPJ
- Busca, filtros e paginação
-
Preços e Estoque
- Tabela de preços por variante
- Controle de estoque por SKU
- Histórico de movimentações
-
Sistema de Vendas
- Pedidos/vendas e itens
- Cálculo de totais
- Histórico por cliente e relatórios básicos
- Relatórios (PDF/Excel)
- API REST para integração
- Migração para .NET 8
- Testes unitários/integrados
- CI/CD com GitHub Actions
O sistema possui um modulo de estoque baseado em eventos, multi-tenant e focado em SKUs (product_variant), com as seguintes pecas principais:
-
Locais de Estoque (
stock_location)- Cada organizacao pode ter varios locais (almoxarifados, lojas, locais virtuais).
- Campos principais:
code,name,is_default,is_virtual,is_active. - Tela em Estoque > Locais para criar/editar locais.
-
Saldo de Estoque (
stock_balance)- Projecao atual de saldo por
organization_id+location_id+variant_id. - Campos:
on_hand(fisico),reserved(reservado),last_movement_at. - Constraint garante que
on_hand >= reservede nunca negativo.
- Projecao atual de saldo por
-
Livro-Razao (
stock_ledger)- Cada movimentacao gera um evento com
delta_on_handedelta_reserved. - Tipos de movimento (enum
StockMovementType):StockReceived(entrada)StockReserved(reserva)StockReleased(liberacao de reserva)StockShipped(saida/baixa)StockAdjusted(ajuste de inventario)StockTransferredOut/StockTransferredIn(transferencia entre locais)
- Cada linha tem
deduplication_keypara idempotencia por organizacao.
- Cada movimentacao gera um evento com
-
Reservas (
stock_reservation)- Representa reservas explicitas de estoque por SKU/local.
- Campos principais:
quantity,status(Active/Cancelled/Consumed),reserved_at,expires_at. - Hoje a UI cria reservas manuais; no futuro, a ideia e vincular a pedidos/empenhos.
-
Outbox de Eventos (
stock_outbox_event)- Implementa o Outbox Pattern para integracao assincrona (Kafka).
- Campos:
aggregate_type,aggregate_id,event_type,payload(JSON),status. - Toda movimentacao grava na outbox dentro da mesma transacao do ledger/saldo.
-
Camada de Servico (
StockService)- Expe operacoes de dominio:
ReceiveAsync(entrada manual)AdjustAsync(ajuste para novo saldo fisico)TransferAsync(entre locais)ReserveAsync/ReleaseReservationAsyncShipAsync(baixa, opcionalmente consumindo reserva)- Consultas: saldos, livro-razão recente, reservas ativas.
- Garante:
- Idempotencia via
deduplication_keygerada no backend. - Consistencia entre
stock_balance,stock_ledgerestock_reservation. - Validacao de tenant: SKU e local precisam pertencer a mesma organizacao.
- Gera automaticamente
source_type = "AJUSTE MANUAL"e umsource_id(GUID) para todas as movimentacoes feitas pela UI.
- Idempotencia via
- Expe operacoes de dominio:
-
UI de Estoque
- Dashboard de Estoque (
/Stock/Index):- Lista saldos por SKU/local (fisico, reservado, disponivel).
- Lista reservas ativas e permite liberacao manual.
- Lista ultimas movimentacoes (livro-razão).
- Movimentacoes Manuais (
/Stock/Receive,/Stock/Adjust,/Stock/Transfer,/Stock/Reserve,/Stock/Ship):- Usuario seleciona SKU, local (e local destino, quando aplicavel), quantidade e um motivo opcional.
- O sistema gera internamente a fonte, o identificador e a chave de idempotencia.
- Locais de Estoque (
/Stock/Locations):- Cadastro e edicao de locais, inclusive marcacao de local padrao.
- Dashboard de Estoque (
Proximos passos planejados para evoluir o modulo:
-
Outbox Relay para Kafka
- Criar um processo/worker (console app ou Windows Service) que:
- Leia registros de
stock_outbox_eventcomstatus = 0(pendente), - Publique os eventos no Kafka usando
Confluent.Kafka, - Atualize
statuspara1(publicado) ou2(erro), registrandoerrorepublished_at.
- Leia registros de
- Permitir configuracao de topico/chave por tipo de evento (ex.:
stock-events,stock-reservations).
- Criar um processo/worker (console app ou Windows Service) que:
-
Worker de Consumo (Kafka -> Outros Contextos)
- Implementar um worker separado que consuma eventos de estoque para:
- Manter projecoes em outros servicos (e-commerce, PNCP, relatorios, etc.).
- Atualizar caches e indices de busca dependentes de disponibilidade.
- Implementar um worker separado que consuma eventos de estoque para:
-
Integracao com Modulos de Negocio
- Substituir gradualmente movimentacoes manuais por integracoes:
- Reservas disparadas por pedidos/vendas.
- Entradas disparadas por compras/recebimento/NFe.
- Baixas disparadas por faturamento/expedicao.
- Substituir gradualmente movimentacoes manuais por integracoes:
Microsoft.Extensions.DependencyInjection gerencia as dependencias:
ApplicationDbContext: Transient (evita problemas de disposed)IPasswordHasher,IEmailService,ISessionService: TransientIAuthenticationService: TransientIStorageService(MinIOStorageService): TransientIImageProcessingService: Transient- Controllers: Transient
Como o projeto usa .NET Framework + EF Core, os binding redirects definidos no Web.config evitam conflitos de versao.
A separacao de dados acontece via memberships: cada usuario pode pertencer a varias organizations, e os relacionamentos trazem organization_id/user_id em todas as tabelas dependentes. Um filtro global de tenant podera ser adicionado futuramente no DbContext.
O sistema utiliza MinIO para armazenamento de arquivos, especialmente fotos de perfil dos usuários:
- Bucket:
user-profiles(criado automaticamente se não existir) - Política: Configurada para leitura pública das imagens
- Serviço:
IStorageServiceabstrai operações de upload/download/delete - Implementação:
MinIOStorageServiceusando SDK oficial do MinIO
Sistema completo de processamento de imagens antes do upload:
- Redimensionamento automático: Máximo 800x800 pixels mantendo proporção
- Otimização: Compressão JPEG em 85% de qualidade
- Validação: Aceita apenas JPG, JPEG e PNG
- Limite de tamanho: 5MB (configurável no Web.config)
- Configurações personalizáveis no Web.config:
Image:MaxWidth: Largura máxima (padrão 800)Image:MaxHeight: Altura máxima (padrão 800)Image:Quality: Qualidade JPEG 0-100 (padrão 85)Image:MaxFileSize: Tamanho máximo em bytes (padrão 5242880)
Projeto de portfolio pessoal, mas feedbacks sao bem-vindos! Abra uma issue ou envie um PR.
Projeto sob licenca MIT.
Mateus Salgueiro
- GitHub: @psielta
- LinkedIn: Mateus Salgueiro
Desenvolvido como parte do meu portfolio de desenvolvimento web com .NET.