first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Auth\Controllers;
|
||||
|
||||
use KrothiumAPI\Utils\HttpUtil;
|
||||
use WorkbloomERP\Utils\SanitizeUtil;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
use WorkbloomERP\Module\v0\Auth\Factories\ServiceFactory;
|
||||
|
||||
class AuthController {
|
||||
/**
|
||||
* Endpoint de API para autenticação de usuário (**Login**).
|
||||
*
|
||||
* Este método gerencia a interface HTTP (POST) para validar as credenciais do usuário.
|
||||
* Ele atua como uma camada fina (*Thin Controller*), responsável por capturar o
|
||||
* payload da requisição, sanitizar os dados de entrada para prevenir injeções
|
||||
* e delegar a verificação de identidade e gestão de sessão/tokens para o `AuthService`.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Captura e Validação:** Extrai o corpo da requisição. Se o payload estiver vazio, interrompe com erro 400 (Bad Request).
|
||||
* 2. **Sanitização:** Aplica `SanitizeUtil::string()` nos campos de login e senha para garantir a limpeza dos dados antes do processamento de negócio.
|
||||
* 3. **Processamento de Negócio:** Invoca `AuthService::login()`, que realiza a consulta de credenciais.
|
||||
* 4. **Normalização de Resposta:**
|
||||
* - **Sucesso (200 OK):** Retorna os dados de sessão (ex: token de acesso).
|
||||
* - **Tratamento de Erros:** Captura `AppException` (credenciais inválidas, conta bloqueada, etc.), formatando a resposta para que o frontend receba um objeto de erro estruturado.
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - A responsabilidade de verificar a hash da senha e gerar o token de acesso reside exclusivamente no `AuthService`, isolando a lógica de segurança do controlador.
|
||||
* - Utiliza `HttpUtil` para garantir consistência no formato da resposta JSON, independentemente do sucesso ou falha da autenticação.
|
||||
*
|
||||
* @return void O método encerra o processamento enviando uma resposta HTTP JSON ao cliente.
|
||||
* @see AuthService::login() Para os detalhes sobre a regra de autenticação e geração de tokens.
|
||||
*/
|
||||
public function login(): void {
|
||||
try {
|
||||
$form = HttpUtil::getRequestBody(form_type: 'POST');
|
||||
if (empty($form)) {
|
||||
throw new AppException(message: 'Requisição inválida.', code: 400);
|
||||
}
|
||||
|
||||
$authService = ServiceFactory::makeAuthService();
|
||||
$response = $authService->login(login: SanitizeUtil::string(value: $form['login']), senha: SanitizeUtil::string(value: $form['senha']));
|
||||
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $response['response_code'] ?? 200,
|
||||
message: $response['message'] ?? 'Login realizado com sucesso.',
|
||||
output: $response['output'] ?? null
|
||||
);
|
||||
} catch(AppException $e) {
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $e->getCode() ?? 500,
|
||||
message: $e->getMessage(),
|
||||
output: $e->getDetails() ? ['errors' => $e->getDetails()] : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint de API para a seleção de contexto de empresa ativa.
|
||||
*
|
||||
* Este método gerencia a interface HTTP (POST) para permitir que um usuário
|
||||
* autenticado alterne sua empresa ativa no sistema multi-empresa. O controlador
|
||||
* realiza a captura do payload, sanitiza o UUID da empresa e delega a
|
||||
* complexa lógica de troca de sessão e regeneração de tokens ao `AuthService`.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Captura e Validação:** Extrai o corpo da requisição. Se o payload estiver vazio ou malformado, retorna erro 400 (Bad Request).
|
||||
* 2. **Sanitização:** Aplica `SanitizeUtil::string()` no `empresa_uuid` para garantir a integridade dos dados recebidos.
|
||||
* 3. **Processamento de Negócio:** Invoca `AuthService::selectCompany()`, que valida o acesso à empresa, gera um novo JWT e registra a sessão.
|
||||
* 4. **Normalização de Resposta:**
|
||||
* - **Sucesso (200 OK):** Retorna o novo token de acesso contendo o contexto da empresa selecionada.
|
||||
* - **Tratamento de Erros:** Captura `AppException` (ex: empresa inexistente, falta de permissão), retornando uma resposta JSON estruturada com os detalhes do erro para o cliente.
|
||||
*
|
||||
* ---
|
||||
* ## Arquitetura de Seleção de Contexto
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - Segue o padrão "Thin Controller", onde a orquestração de infraestrutura (cache, banco de dados, JWT) é encapsulada na camada de serviço.
|
||||
* - Este endpoint é fundamental para aplicações multi-tenant ou multi-empresa, permitindo que o usuário altere seu "escopo" de atuação sem precisar realizar um re-login completo.
|
||||
*
|
||||
* @return void O método encerra o processamento enviando uma resposta HTTP JSON ao cliente.
|
||||
* @see AuthService::selectCompany() Para os detalhes sobre a regra de negócio aplicada na troca de contexto.
|
||||
*/
|
||||
public function selectCompany(): void {
|
||||
try {
|
||||
$form = HttpUtil::getRequestBody(form_type: 'POST');
|
||||
if (empty($form)) {
|
||||
throw new AppException(message: 'Requisição inválida.', code: 400);
|
||||
}
|
||||
|
||||
$authService = ServiceFactory::makeAuthService();
|
||||
$response = $authService->selectCompany(empresa_uuid: SanitizeUtil::string(value: $form['empresa_uuid']));
|
||||
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $response['response_code'] ?? 200,
|
||||
message: $response['message'] ?? 'Empresa selecionada com sucesso.',
|
||||
output: $response['output'] ?? null
|
||||
);
|
||||
} catch(AppException $e) {
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $e->getCode() ?? 500,
|
||||
message: $e->getMessage(),
|
||||
output: $e->getDetails() ? ['errors' => $e->getDetails()] : null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Auth\Factories;
|
||||
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Auth\Services\AuthService;
|
||||
use WorkbloomERP\Module\v0\Empresa\Repos\EmpresaRepo;
|
||||
use WorkbloomERP\Module\v0\Usuario\Repos\UsuarioRepo;
|
||||
use WorkbloomERP\Module\v0\Usuario\Repos\UsuarioSessionRepo;
|
||||
use WorkbloomERP\Module\v0\Usuario\Repos\UsuarioEmpresaRepo;
|
||||
|
||||
class ServiceFactory {
|
||||
public static function makeAuthService() {
|
||||
$db = new DBService();
|
||||
return new AuthService(
|
||||
db: $db,
|
||||
usuarioRepo: new UsuarioRepo(db: $db),
|
||||
empresaRepo: new EmpresaRepo(db: $db),
|
||||
usuarioSessionRepo: new UsuarioSessionRepo(db: $db),
|
||||
usuarioEmpresaRepo: new UsuarioEmpresaRepo(db: $db),
|
||||
);
|
||||
}
|
||||
|
||||
public static function makeUsuarioSessionRepo() {
|
||||
$db = new DBService();
|
||||
return new UsuarioSessionRepo(
|
||||
db: $db
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Auth\Middlewares;
|
||||
|
||||
use WorkbloomERP\Utils\JwtUtil;
|
||||
use KrothiumAPI\Utils\HttpUtil;
|
||||
use WorkbloomERP\Utils\CacheUtil;
|
||||
use WorkbloomERP\Utils\CypherUtil;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
use WorkbloomERP\Module\v0\Auth\Factories\ServiceFactory;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioSessionModel;
|
||||
|
||||
class AuthMiddleware {
|
||||
/**
|
||||
* Valida se o token JWT da requisição atual corresponde a um estado de "Login Inicial".
|
||||
*
|
||||
* Este método atua como um guardião de segurança (Guard) para rotas que exigem
|
||||
* ações obrigatórias do usuário logo após o primeiro acesso ao sistema, como a
|
||||
* troca obrigatória de senha ou a configuração de MFA (Autenticação de Dois Fatores).
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Extração:** Obtém o token JWT do cabeçalho `Authorization: Bearer`.
|
||||
* 2. **Validação:** Verifica a integridade e assinatura do token através do `JwtUtil`.
|
||||
* 3. **Verificação de Claim:** Decodifica o token e confirma se a claim `initial_login` está presente e definida como `true`.
|
||||
* 4. **Retorno:**
|
||||
* - Retorna `true` se o token for válido e corresponder ao estado de login inicial.
|
||||
* - Retorna um `array` estruturado contendo detalhes do erro caso a validação falhe.
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - O método utiliza variáveis de ambiente (`$_ENV`) para configurar o `JwtUtil`, garantindo que as configurações de `secretKey`, `algorithm` e `ttl` estejam sincronizadas com a política de segurança da aplicação.
|
||||
* - Este método captura internamente instâncias de `AppException`, tratando o fluxo de erro de forma silenciosa para o chamador, retornando um status de erro estruturado em vez de propagar a exceção.
|
||||
*
|
||||
* @return true|array{status: string, response_code: int, message: string}
|
||||
* Retorna `true` em caso de sucesso. Retorna um array com o erro caso o token seja inválido, ausente ou não possua a claim necessária.
|
||||
*/
|
||||
public static function isInitialLogin(): true|array {
|
||||
try {
|
||||
// Valida o token JWT presente no header Authorization (Bearer Token)
|
||||
|
||||
$bearerToken = HttpUtil::getBearerToken();
|
||||
if (!$bearerToken) {
|
||||
throw new AppException(message: 'Token de acesso inválido ou ausente.', code: 401);
|
||||
}
|
||||
|
||||
$jwtUtil = new JwtUtil(
|
||||
secretKey: $_ENV['JWT_SECRET_KEY'], algorithm: $_ENV['JWT_ALGORITHM'],
|
||||
ttl: $_ENV['JWT_EXPIRATION_TIME'], issuer: $_ENV['JWT_ISSUER']
|
||||
);
|
||||
$decodedToken = $jwtUtil->validate(token: $bearerToken);
|
||||
if (!$decodedToken) {
|
||||
throw new AppException(message: 'Token de acesso inválido ou ausente.', code: 401);
|
||||
}
|
||||
|
||||
$decodedToken = json_decode(json: json_encode($decodedToken, flags: JSON_THROW_ON_ERROR), associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$userData = $decodedToken['user_data'] ?? null;
|
||||
if (!$userData) {
|
||||
throw new AppException(message: 'Token de acesso inválido ou ausente.', code: 401);
|
||||
}
|
||||
|
||||
if (!isset($userData['initial_login']) || $userData['initial_login'] !== true) {
|
||||
throw new AppException(message: 'Token de acesso não corresponde a um login inicial.', code: 403);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(AppException $e) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'response_code' => $e->getCode() ?? 500,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware de autenticação para validar sessões e tokens JWT em requisições protegidas.
|
||||
*
|
||||
* Este método atua como um "Gatekeeper" (Guard). Ele implementa uma estratégia híbrida
|
||||
* de validação (Cache-First, seguida de DB e JWT) para equilibrar performance com
|
||||
* segurança. Ele verifica a existência do token, a validade da assinatura JWT,
|
||||
* a revogação da sessão no banco de dados e a integridade do dispositivo (IP/User-Agent).
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Extração:** Obtém o token do header `Authorization`. Retorna erro 401 se ausente.
|
||||
* 2. **Cache-First Check:** Verifica no Redis (`login:main:{token}`). Se encontrado, autoriza imediatamente (retorna `null`), evitando carga no banco de dados.
|
||||
* 3. **Validação de Persistência:** Consulta o `UsuarioSessionRepo` (via hash do token). Valida revogação (`revoked_at`) e integridade.
|
||||
* 4. **Validação JWT:** Decodifica e verifica a assinatura/expiração (`exp`) do JWT.
|
||||
* 5. **Validação de Integridade (Guard):** Invoca `validateSessionData` para garantir que o IP e User-Agent atuais coincidem com a sessão original.
|
||||
* 6. **Caching:** Se tudo estiver correto, armazena o payload no cache para acelerar futuras requisições e retorna `null` (acesso concedido).
|
||||
*
|
||||
* ---
|
||||
* ## Arquitetura de Validação
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - **Interface de Retorno:**
|
||||
* - `null`: A autenticação foi bem-sucedida; o fluxo de execução pode continuar.
|
||||
* - `array`: A autenticação falhou; o array contém a resposta de erro estruturada para o cliente.
|
||||
* - **Segurança:** Utiliza `CypherUtil` para garantir que tokens sensíveis não sejam comparados em texto puro contra o banco de dados.
|
||||
*
|
||||
* @return array|null Retorna `null` se o usuário estiver autenticado e autorizado. Retorna um `array` com `response_code`, `message` e `output` em caso de erro.
|
||||
* @see JwtUtil::validate() Para detalhes sobre a validação da assinatura criptográfica.
|
||||
* @see self::validateSessionData() Para a verificação de segurança de IP e User-Agent.
|
||||
*/
|
||||
public static function handle(): ?array {
|
||||
try {
|
||||
// Valida o token JWT presente no header Authorization (Bearer Token)
|
||||
$bearerToken = HttpUtil::getBearerToken();
|
||||
if (!$bearerToken) {
|
||||
throw new AppException(message: 'Token de acesso inválido ou ausente.', code: 401);
|
||||
}
|
||||
|
||||
$jwtUtil = new JwtUtil(
|
||||
secretKey: $_ENV['JWT_SECRET_KEY'], algorithm: $_ENV['JWT_ALGORITHM'],
|
||||
ttl: $_ENV['JWT_EXPIRATION_TIME'], issuer: $_ENV['JWT_ISSUER']
|
||||
);
|
||||
|
||||
// Valida o token JWT e decodifica seu payload para extrair os dados do usuário e as informações de sessão.
|
||||
$decodedToken = $jwtUtil->validate(token: $bearerToken);
|
||||
if (!$decodedToken) {
|
||||
throw new AppException(message: 'Token de acesso inválido ou ausente.', code: 401);
|
||||
}
|
||||
|
||||
// Verifica se o token corresponde a um login inicial, bloqueando o acesso a rotas protegidas caso seja verdadeiro.
|
||||
$decodedToken = json_decode(json: json_encode($decodedToken, flags: JSON_THROW_ON_ERROR), associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
if ($decodedToken['user_data']['initial_login'] == true) {
|
||||
throw new AppException(message: 'Acesso negado. O token corresponde a um login inicial e não pode ser usado para acessar esta rota.', code: 403);
|
||||
}
|
||||
|
||||
// Verifica se a sessão principal já está em cache para evitar consultas desnecessárias ao banco de dados e validações repetitivas.
|
||||
// Se a sessão estiver em cache, considera o token como válido e retorna null para permitir o acesso à rota protegida.
|
||||
if (CacheUtil::get(key: "login:main:{$bearerToken}")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$usuarioSessionRepo = ServiceFactory::makeUsuarioSessionRepo();
|
||||
|
||||
// Busca a sessão do usuário no banco de dados utilizando o hash do token para garantir que o token é válido e não foi revogado.
|
||||
$usuarioSessionModel = $usuarioSessionRepo->findByIdentifier(identifier: 'token_hash', value: CypherUtil::hash(data: $bearerToken));
|
||||
if (!$usuarioSessionModel || !CypherUtil::verify(data: $bearerToken, hash: $usuarioSessionModel->getTokenHash()) || $usuarioSessionModel->getRevokedAt()) {
|
||||
throw new AppException(message: 'Sessão inválida ou expirada. Faça login novamente.', code: 401);
|
||||
}
|
||||
|
||||
// Valida o token JWT e decodifica seu payload para extrair os dados do usuário e as informações de sessão.
|
||||
// Se o token for inválido, expirado ou tiver sua assinatura comprometida, uma exceção é lançada.
|
||||
$decodedToken = $jwtUtil->validate(token: $bearerToken);
|
||||
if (!$decodedToken) {
|
||||
throw new AppException(message: 'Token de acesso inválido ou ausente.', code: 401);
|
||||
}
|
||||
|
||||
$decodedToken = json_decode(json: json_encode($decodedToken, JSON_THROW_ON_ERROR), associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$remainingTime = $decodedToken['exp'] - time();
|
||||
if ($remainingTime <= 0) {
|
||||
throw new AppException(message: 'Sessão expirada. Faça login novamente.', code: 401);
|
||||
}
|
||||
|
||||
self::validateSessionData(sessionModel: $usuarioSessionModel, tokenData: $decodedToken);
|
||||
CacheUtil::set(key: "login:main:{$bearerToken}", ttl: $remainingTime, value: $decodedToken);
|
||||
return null;
|
||||
} catch (AppException $e) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'response_code' => $e->getCode() ?: 500,
|
||||
'message' => $e->getMessage(),
|
||||
'output' => $e->getDetails() ? ['errors' => $e->getDetails()] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida a integridade e autenticidade da sessão do usuário comparando metadados.
|
||||
*
|
||||
* Este método atua como uma barreira de segurança (Guard) que garante que a sessão
|
||||
* atual está sendo utilizada pelo mesmo dispositivo e rede que a iniciou. Ao comparar
|
||||
* o IP e o User-Agent da requisição atual com os dados persistidos no banco de dados
|
||||
* (`UsuarioSessionModel`) e os metadados contidos no token JWT (`$tokenData`),
|
||||
* protegemos o sistema contra ataques de sequestro de sessão (session hijacking).
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Captura:** Extrai o IP (`REMOTE_ADDR`) e o User-Agent (`HTTP_USER_AGENT`) da variável superglobal `$_SERVER`.
|
||||
* 2. **Comparação (Match):** Utiliza uma expressão `match` para validar quatro pontos de convergência:
|
||||
* - IP da Sessão (DB) vs. IP Atual.
|
||||
* - User-Agent da Sessão (DB) vs. User-Agent Atual.
|
||||
* - IP registrado no Payload do Token vs. IP Atual.
|
||||
* - User-Agent registrado no Payload do Token vs. User-Agent Atual.
|
||||
* 3. **Bloqueio:** Caso qualquer divergência seja detectada, a sessão é considerada inválida e uma `AppException` (401 Unauthorized) é disparada.
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - O uso de `match` (PHP 8+) torna o código altamente legível e performático para a verificação booleana múltipla.
|
||||
* - Este método é fundamental em cenários onde a segurança de acesso exige que o usuário não troque de rede ou navegador enquanto a sessão estiver ativa.
|
||||
* - Caso a requisição venha através de um proxy, certifique-se de que a configuração de captura de IP (`REMOTE_ADDR`) esteja ajustada corretamente no ambiente.
|
||||
*
|
||||
* @param UsuarioSessionModel $sessionModel O modelo contendo os dados da sessão persistidos no banco.
|
||||
* @param array $tokenData O payload decodificado do token JWT contendo os metadados da sessão.
|
||||
* @return void
|
||||
* @throws AppException Caso haja qualquer incompatibilidade nos dados (IP ou User-Agent), invalidando a sessão.
|
||||
*/
|
||||
private static function validateSessionData(UsuarioSessionModel $sessionModel, array $tokenData): void {
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||||
|
||||
$isInvalidSession = match(true) {
|
||||
$sessionModel->getIpAddress() !== $ipAddress,
|
||||
$sessionModel->getUserAgent() !== $userAgent,
|
||||
$tokenData['session_data']['ip_address'] !== $ipAddress,
|
||||
$tokenData['session_data']['user_agent'] !== $userAgent => true,
|
||||
default => false
|
||||
};
|
||||
if ($isInvalidSession) {
|
||||
throw new AppException(message: 'Sessão inválida ou expirada. Faça login novamente.', code: 401);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
use KrothiumAPI\Http\Router;
|
||||
use WorkbloomERP\Module\v0\Auth\Middlewares\AuthMiddleware;
|
||||
use WorkbloomERP\Module\v0\Auth\Controllers\AuthController;
|
||||
|
||||
Router::group(
|
||||
prefix: '/auth',
|
||||
callback: function() {
|
||||
// Rota de autenticação
|
||||
Router::post(uri: '/login', handler: [AuthController::class, 'login']);
|
||||
|
||||
// Rota para seleção de empresa após login inicial
|
||||
Router::post(uri: '/select-company', handler: [AuthController::class, 'selectCompany'], middlewares: [[AuthMiddleware::class, 'isInitialLogin']]);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Auth\Services;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use DateTimeImmutable;
|
||||
use KrothiumAPI\Utils\HttpUtil;
|
||||
use WorkbloomERP\Utils\JwtUtil;
|
||||
use WorkbloomERP\Utils\CacheUtil;
|
||||
use WorkbloomERP\Utils\CypherUtil;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
use WorkbloomERP\Module\v0\Auth\Utils\AuthUtil;
|
||||
use WorkbloomERP\Module\v0\Empresa\Repos\EmpresaRepo;
|
||||
use WorkbloomERP\Module\v0\Usuario\Repos\UsuarioRepo;
|
||||
use WorkbloomERP\Module\v0\Usuario\Repos\UsuarioSessionRepo;
|
||||
use WorkbloomERP\Module\v0\Usuario\Repos\UsuarioEmpresaRepo;
|
||||
use WorkbloomERP\Module\v0\Empresa\Models\EmpresaModel;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioModel;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioSessionModel;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioEmpresaModel;
|
||||
|
||||
class AuthService {
|
||||
public function __construct(
|
||||
private DBService $db,
|
||||
private UsuarioRepo $usuarioRepo,
|
||||
private EmpresaRepo $empresaRepo,
|
||||
private UsuarioSessionRepo $usuarioSessionRepo,
|
||||
private UsuarioEmpresaRepo $usuarioEmpresaRepo,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Realiza a autenticação de usuário e inicia uma sessão no sistema.
|
||||
*
|
||||
* Este método é o coração do processo de autenticação. Ele valida as credenciais
|
||||
* fornecidas, gera um token JWT (JSON Web Token) para comunicação segura, persiste
|
||||
* a sessão no banco de dados para fins de auditoria/rastreabilidade e armazena
|
||||
* o token no cache (Redis) para validação rápida de requests subsequentes.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Validação de Entrada:** Verifica se `login` e `senha` foram fornecidos.
|
||||
* 2. **Autenticação:** Busca o usuário pelo login e utiliza `password_verify` para validar a hash da senha.
|
||||
* 3. **Geração de Token:** Instancia o `JwtUtil` com configurações de ambiente e gera um novo token JWT.
|
||||
* 4. **Persistência de Sessão (Atomicidade):**
|
||||
* - Insere o registro de sessão na tabela `usuario_session` (contendo dados como IP e User-Agent).
|
||||
* - O hash do token é armazenado para permitir revogação futura.
|
||||
* 5. **Caching:** Armazena o token no Redis com a mesma TTL da expiração do token para otimizar a validação de acesso.
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - **Transacionalidade:** Toda a criação de sessão e persistência ocorre dentro de uma transação de banco de dados, garantindo que não existam sessões órfãs caso o cache ou a criação do token falhem.
|
||||
* - **Segurança:** O token JWT gerado é hasheado (`CypherUtil::hash`) antes de ser salvo no banco para evitar a exposição do token real em caso de vazamento de dados.
|
||||
* - **Infraestrutura:** Requer acesso funcional a: `JWT_SECRET_KEY`, `JWT_ALGORITHM`, `JWT_EXPIRATION_TIME`, `JWT_ISSUER`.
|
||||
*
|
||||
* @param string $login O nome de usuário ou e-mail.
|
||||
* @param string $senha A senha em texto puro fornecida pelo usuário.
|
||||
* @return array{response_code: int, message: string, output: array{token: string}} Resposta estruturada contendo o código de sucesso e o token JWT.
|
||||
* @throws AppException Caso as credenciais sejam inválidas (401), o payload esteja vazio (400) ou falhas críticas ocorram na geração de token/sessão (500).
|
||||
*/
|
||||
public function login(string $login, string $senha): array {
|
||||
try {
|
||||
// Validação básica de entrada
|
||||
if (empty($login) || empty($senha)) {
|
||||
throw new AppException(message: 'Login e senha são obrigatórios.', code: 400);
|
||||
}
|
||||
|
||||
// Inicia uma transação para garantir a atomicidade das operações de autenticação
|
||||
return $this->db->transaction(
|
||||
callback: function() use ($login, $senha) {
|
||||
$usuarioModel = $this->usuarioRepo->findByLogin(login: $login);
|
||||
if (!$usuarioModel || !password_verify(password: $senha, hash: $usuarioModel->getSenhaHash())) {
|
||||
throw new AppException(message: 'Login ou senha inválidos.', code: 401);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'user_data' => [
|
||||
'initial_login' => true,
|
||||
'uuid' => $usuarioModel->getUuid(),
|
||||
'nome_usuario' => $usuarioModel->getNomeCompleto(),
|
||||
'email' => $usuarioModel->getEmail(),
|
||||
],
|
||||
'exp' => (new DateTimeImmutable())->modify("+5 minutes")->getTimestamp(),
|
||||
];
|
||||
|
||||
$jwtUtil = new JwtUtil(
|
||||
secretKey: $_ENV['JWT_SECRET_KEY'], algorithm: $_ENV['JWT_ALGORITHM'],
|
||||
ttl: $_ENV['JWT_EXPIRATION_TIME'], issuer: $_ENV['JWT_ISSUER']
|
||||
);
|
||||
$jwtToken = $jwtUtil->generate(payload: $payload);
|
||||
if (!$jwtToken) {
|
||||
throw new AppException(message: 'Erro ao gerar token de acesso.', code: 500);
|
||||
}
|
||||
|
||||
// Armazena o token JWT no cache Redis com uma TTL de 10 minutos (600 segundos)
|
||||
CacheUtil::set(key: "login:init:{$jwtToken}", ttl: 600, value: $payload);
|
||||
|
||||
return [
|
||||
'response_code' => 200,
|
||||
'message' => 'Login realizado com sucesso.',
|
||||
'output' => [
|
||||
'tmp_token' => $jwtToken,
|
||||
'empresas' => $this->getEmpresasByUsuarioId($usuarioModel->getId())
|
||||
]
|
||||
];
|
||||
}
|
||||
);
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera e organiza a lista de empresas vinculadas a um determinado usuário.
|
||||
*
|
||||
* Este método realiza o mapeamento entre o ID do usuário e suas respectivas
|
||||
* empresas. Ele filtra as associações através do repositório `usuarioEmpresaRepo`,
|
||||
* hidrata os objetos `EmpresaModel` e organiza os dados em uma estrutura
|
||||
* associativa agrupada pelo nome empresarial.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Busca:** Recupera todos os registros de associação `usuario_id` -> `empresa_id`.
|
||||
* 2. **Hidratação:** Itera sobre as associações, buscando o `EmpresaModel` completo para cada ID encontrado.
|
||||
* 3. **Estruturação:** Monta um array multidimensional onde as chaves são os nomes das empresas e os valores são arrays contendo metadados (UUID, nome, CNPJ, tipo formatado).
|
||||
* 4. **Tratamento de Dados:** Normaliza o campo `tipo` (MATRIZ/FILIAL) para o formato "Capitalizado" (ex: Matriz).
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - Caso o usuário não possua empresas vinculadas ou o repositório retorne vazio, o método retorna um array vazio.
|
||||
* - Ignora silenciosamente qualquer `empresa_id` que não resulte em um `EmpresaModel` válido (falha de integridade referencial).
|
||||
* - O agrupamento pelo nome empresarial facilita o consumo da estrutura pelo frontend.
|
||||
*
|
||||
* @param int $usuario_id O identificador numérico interno do usuário.
|
||||
* @return array<string, array<int, array{uuid: string, nome_empresarial: string, nome_fantasia: string, cnpj: string, tipo: string}>> Array estruturado de empresas.
|
||||
* @throws AppException Caso ocorra falha de acesso aos repositórios.
|
||||
*/
|
||||
private function getEmpresasByUsuarioId(int $usuario_id): array {
|
||||
try {
|
||||
$empresas = $this->usuarioEmpresaRepo->findAllByUsuarioId(usuario_id: $usuario_id);
|
||||
if (empty($empresas)) {
|
||||
return ['Nenhuma empresa vinculada ao usuário.'];
|
||||
}
|
||||
|
||||
$preparedEmpresas = [];
|
||||
foreach($empresas as $i => $empresa) {
|
||||
$empresaModel = $this->empresaRepo->findByIdentifier(identifier: 'id', value: $empresa['empresa_id']);
|
||||
if (!$empresaModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preparedEmpresas[$empresaModel->getNomeEmpresarial()][] = [
|
||||
'uuid' => $empresaModel->getUuid(),
|
||||
'nome_empresarial' => $empresaModel->getNomeEmpresarial(),
|
||||
'nome_fantasia' => $empresaModel->getNomeFantasia(),
|
||||
'cnpj' => $empresaModel->getDocumentCnpj(),
|
||||
'tipo' => ucfirst(string: strtolower(string: $empresaModel->getTipo()))
|
||||
];
|
||||
}
|
||||
|
||||
return $preparedEmpresas;
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seleciona e define a empresa ativa para o contexto da sessão do usuário.
|
||||
*
|
||||
* Este método é utilizado em sistemas multi-empresa onde o usuário precisa alternar
|
||||
* entre diferentes entidades (empresas) sem realizar um novo login completo.
|
||||
* O método valida se o usuário possui vínculo com a empresa solicitada, gera
|
||||
* um novo token JWT com o contexto da empresa atualizado e registra essa nova
|
||||
* sessão no banco de dados e no cache (Redis).
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Verificação de Sessão:** Garante que o usuário já possui um login ativo (leitura via `AuthUtil`).
|
||||
* 2. **Validação de Acesso:** Verifica se o usuário solicitado possui associação ativa com a empresa (`usuarioEmpresaRepo`).
|
||||
* 3. **Contextualização:** Monta um novo payload contendo os dados do usuário e os dados da empresa selecionada.
|
||||
* 4. **Token Rotation:** Gera um novo token JWT com os novos dados de contexto e define um tempo de expiração (60 minutos).
|
||||
* 5. **Persistência:** Registra a nova sessão no banco de dados (`usuario_session`) e atualiza o estado no cache (`CacheUtil`).
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - **Token Rotation:** O método invalida implicitamente o contexto anterior ao gerar um novo token baseado na nova seleção.
|
||||
* - **Atomicidade:** A operação ocorre dentro de uma transação, garantindo que se o token não for gerado ou a sessão não for criada, nada é alterado.
|
||||
* - **Segurança:** O hash do token gerado é armazenado para permitir rastreabilidade e segurança.
|
||||
*
|
||||
* @param string $empresa_uuid O Identificador Único Universal da empresa que o usuário deseja selecionar.
|
||||
* @return array{response_code: int, message: string, output: array{token: string}} Resposta estruturada contendo o novo token de acesso com o contexto atualizado.
|
||||
* @throws AppException Caso a sessão seja inválida, a empresa não exista, o usuário não tenha permissão de acesso à empresa ou falhas de persistência ocorram.
|
||||
*/
|
||||
public function selectCompany(string $empresa_uuid) {
|
||||
try {
|
||||
return $this->db->transaction(
|
||||
callback: function() use ($empresa_uuid) {
|
||||
if (empty($empresa_uuid)) {
|
||||
throw new AppException(message: 'UUID da empresa é obrigatório.', code: 400);
|
||||
}
|
||||
|
||||
// Lê os dados da sessão atual para validar a existência de um login prévio e obter o UUID do usuário
|
||||
$sessionData = AuthUtil::readInitSession();
|
||||
if (!$sessionData || !isset($sessionData['user_data']['uuid'])) {
|
||||
throw new AppException(message: 'Sessão de usuário não encontrada. Faça login novamente.', code: 401);
|
||||
}
|
||||
|
||||
// Valida se o usuário tem acesso à empresa selecionada
|
||||
$usuarioModel = $this->usuarioRepo->findByIdentifier(identifier: 'uuid', value: $sessionData['user_data']['uuid']);
|
||||
if (!$usuarioModel) {
|
||||
throw new AppException(message: 'Ocorreu um erro ao selecionar a empresa. Tente novamente mais tarde.', code: 404);
|
||||
}
|
||||
|
||||
// Valida se a empresa existe e se o usuário tem associação com ela
|
||||
$empresaModel = $this->empresaRepo->findByIdentifier(identifier: 'uuid', value: $empresa_uuid);
|
||||
if (!$empresaModel || !$this->usuarioEmpresaRepo->checkAssociationByUsuarioIdAndEmpresaId(usuario_id: $usuarioModel->getId(), empresa_id: $empresaModel->getId())) {
|
||||
throw new AppException(message: 'Empresa não encontrada.', code: 404);
|
||||
}
|
||||
|
||||
// Monta o payload da sessão principal, incluindo os dados do usuário e da empresa selecionada
|
||||
$payload = [
|
||||
'user_data' => [
|
||||
'initial_login' => false,
|
||||
'uuid' => $usuarioModel->getUuid(),
|
||||
'nome_completo' => $usuarioModel->getNomeCompleto(),
|
||||
'nome_usuario' => $usuarioModel->getNomeUsuario(),
|
||||
'email' => $usuarioModel->getEmail(),
|
||||
'is_root' => $usuarioModel->getIsRoot() ? true : false
|
||||
],
|
||||
'empresa_data' => [
|
||||
'uuid' => $empresaModel->getUuid(),
|
||||
'nome_empresarial' => $empresaModel->getNomeEmpresarial(),
|
||||
'nome_fantasia' => $empresaModel->getNomeFantasia(),
|
||||
'cnpj' => $empresaModel->getDocumentCnpj(),
|
||||
'tipo' => ucfirst(string: strtolower(string: $empresaModel->getTipo()))
|
||||
],
|
||||
'session_data' => [
|
||||
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
|
||||
],
|
||||
'iat' => (new DateTimeImmutable())->getTimestamp(),
|
||||
'exp' => (new DateTimeImmutable())->modify("+{$_ENV['JWT_EXPIRATION_TIME']} seconds")->getTimestamp(),
|
||||
];
|
||||
|
||||
$jwtUtil = new JwtUtil(
|
||||
secretKey: $_ENV['JWT_SECRET_KEY'], algorithm: $_ENV['JWT_ALGORITHM'],
|
||||
ttl: $_ENV['JWT_EXPIRATION_TIME'], issuer: $_ENV['JWT_ISSUER']
|
||||
);
|
||||
$jwtToken = $jwtUtil->generate(payload: $payload);
|
||||
if (!$jwtToken) {
|
||||
throw new AppException(message: 'Erro ao gerar token de acesso.', code: 500);
|
||||
}
|
||||
|
||||
$usuarioSessionModel = $this->usuarioSessionRepo->insert(
|
||||
new UsuarioSessionModel(
|
||||
uuid: Uuid::uuid7()->toString(),
|
||||
usuario_id: $usuarioModel->getId(),
|
||||
token_hash: CypherUtil::hash(data: $jwtToken),
|
||||
ip_address: $payload['session_data']['ip_address'],
|
||||
user_agent: $payload['session_data']['user_agent'],
|
||||
created_at: new DateTimeImmutable()
|
||||
)
|
||||
);
|
||||
if (!$usuarioSessionModel) {
|
||||
throw new AppException(message: 'Erro ao criar sessão de usuário.', code: 500);
|
||||
}
|
||||
|
||||
if (!CacheUtil::set(key: "login:main:{$jwtToken}", ttl: $_ENV['JWT_EXPIRATION_TIME'], value: $payload)) {
|
||||
throw new AppException(message: 'Erro ao armazenar sessão no cache.', code: 500);
|
||||
}
|
||||
|
||||
$beforeToken = HttpUtil::getBearerToken();
|
||||
if (!CacheUtil::delete(keys: ["login:init:{$beforeToken}"])) {
|
||||
throw new AppException(message: 'Erro ao limpar sessão temporária do cache.', code: 500);
|
||||
}
|
||||
|
||||
return [
|
||||
'response_code' => 200,
|
||||
'message' => 'Empresa selecionada com sucesso.',
|
||||
'output' => [
|
||||
'token' => $jwtToken
|
||||
]
|
||||
];
|
||||
}
|
||||
);
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Auth\Utils;
|
||||
|
||||
use KrothiumAPI\Utils\HttpUtil;
|
||||
use WorkbloomERP\Utils\JwtUtil;
|
||||
use WorkbloomERP\Utils\CacheUtil;
|
||||
use WorkbloomERP\Utils\CryptoUtil;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioModel;
|
||||
use WorkbloomERP\Module\v0\Auth\Factories\ServiceFactory;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioSessionModel;
|
||||
|
||||
class AuthUtil {
|
||||
/**
|
||||
* Recupera os dados de uma sessão de "Login Inicial" a partir do cache.
|
||||
*
|
||||
* Este método é utilizado para verificar se existe uma sessão ativa vinculada ao
|
||||
* token de acesso atual no fluxo de "Primeiro Acesso" (Initial Login). Ele permite
|
||||
* identificar rapidamente se o usuário está em um estado que exige ações
|
||||
* obrigatórias, como a alteração de senha inicial ou configuração de MFA,
|
||||
* sem a necessidade de consultar o banco de dados.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Identificação:** Extrai o token Bearer da requisição atual (`HttpUtil::getBearerToken()`).
|
||||
* 2. **Busca em Cache:** Consulta o serviço de cache (Redis) utilizando a chave prefixada `login:init:{token}`.
|
||||
* 3. **Retorno:** Devolve os dados da sessão (array) caso o registro exista e esteja ativo; retorna `null` caso a sessão não exista ou tenha expirado.
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - **Performance:** Este método é altamente performático por consultar exclusivamente a camada de cache, sendo ideal para middlewares de validação de fluxo de login.
|
||||
* - **Segurança:** A chave de busca utiliza o próprio token Bearer, garantindo que o acesso aos dados da sessão inicial seja restrito ao detentor do token correspondente.
|
||||
* - **Integração:** Este método deve ser utilizado em conjunto com o fluxo de autenticação que persiste os dados do login inicial no cache.
|
||||
*
|
||||
* @return array|null Retorna o payload da sessão de login inicial se encontrada; retorna `null` se a sessão não existir no cache.
|
||||
*/
|
||||
public static function readInitSession(): ?array {
|
||||
// Verifica se existe um login inicial
|
||||
$bearerToken = HttpUtil::getBearerToken();
|
||||
$initSession = CacheUtil::get(key: "login:init:{$bearerToken}");
|
||||
if ($initSession) {
|
||||
return $initSession;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera a sessão principal do usuário ou uma chave específica do payload armazenado em cache.
|
||||
*
|
||||
* Este método centraliza o acesso aos dados de sessão ativos. Ele utiliza o
|
||||
* token Bearer da requisição atual para localizar o payload no cache (Redis) e,
|
||||
* caso uma chave específica seja fornecida, retorna apenas o subconjunto de dados
|
||||
* solicitado, otimizando o acesso às informações de contexto.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Identificação:** Obtém o token de acesso (Bearer Token) da requisição atual via `HttpUtil`.
|
||||
* 2. **Busca:** Consulta o cache usando a chave padronizada `login:main:{token}`.
|
||||
* 3. **Filtragem:**
|
||||
* - Se uma `$key` for fornecida e existir no payload, retorna apenas o valor correspondente (ex: `user_data`).
|
||||
* - Se nenhuma chave for fornecida ou a chave não existir, retorna o payload completo.
|
||||
* 4. **Retorno:** Devolve `null` caso a sessão não esteja em cache (sessão inexistente ou expirada).
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - **Performance:** A consulta ao cache é direta e rápida. O método evita redundância ao permitir filtrar os dados necessários logo na leitura.
|
||||
* - **Flexibilidade:** A inclusão do parâmetro `$key` permite que outros serviços solicitem apenas o bloco de dados que precisam (ex: dados da empresa ou do usuário), reduzindo o processamento desnecessário de arrays grandes.
|
||||
*
|
||||
* @param string $key Chave específica a ser extraída do payload da sessão (opcional; se vazio, retorna o payload completo).
|
||||
* @return array|null Retorna o payload da sessão ou um subconjunto dele; retorna `null` se a sessão não existir no cache.
|
||||
*/
|
||||
public static function readSession(string $key): ?array {
|
||||
$bearerToken = HttpUtil::getBearerToken();
|
||||
$result = CacheUtil::get(key: "login:main:{$bearerToken}");
|
||||
|
||||
if (!$result) {return null;}
|
||||
|
||||
if ($key && isset($result[$key])) {
|
||||
return $result[$key];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Constants;
|
||||
|
||||
class ContribuinteICMSConst {
|
||||
public const CONTATO_CONTRIBUINTE_ICMS = [
|
||||
1 => 'Contribuinte ICMS',
|
||||
2 => 'Contribuinte Isento de Inscrição no Cadastro de Contribuintes',
|
||||
9 => 'Não Contribuinte, que pode ou não possuir Inscrição Estadual no Cadastro de Contribuintes'
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::CONTATO_CONTRIBUINTE_ICMS;
|
||||
}
|
||||
|
||||
public static function isValid(string $value): bool {
|
||||
return array_key_exists(key: $value, array: self::CONTATO_CONTRIBUINTE_ICMS);
|
||||
}
|
||||
|
||||
public static function getDescription(string $key): ?string {
|
||||
return self::CONTATO_CONTRIBUINTE_ICMS[$key] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Constants;
|
||||
|
||||
class NFSeConsumoIbsCbsConst {
|
||||
public const CONTATO_USO_CONSUMO_IBS_CBS = [
|
||||
'1' => 'Sim (Consumidor Final)',
|
||||
'2' => 'Não informar',
|
||||
'0' => 'Não (Operação B2B)',
|
||||
];
|
||||
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::CONTATO_USO_CONSUMO_IBS_CBS;
|
||||
}
|
||||
|
||||
public static function getDescription(string|int|null $key): string {
|
||||
return self::CONTATO_USO_CONSUMO_IBS_CBS[$key] ?? 'Valor desconhecido';
|
||||
}
|
||||
|
||||
public static function isValid(string|int|null $key): bool {
|
||||
return array_key_exists(key: $key, array: self::CONTATO_USO_CONSUMO_IBS_CBS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Constants;
|
||||
|
||||
class OrgaoPublicoConst {
|
||||
public const CONTATO_ORGAO_PUBLICO = [
|
||||
'NAO' => 'Não',
|
||||
'FEDERAL' => 'Órgão Público Federal',
|
||||
'ESTADUAL' => 'Órgão Público Estadual',
|
||||
'MUNICIPAL' => 'Órgão Público Municipal'
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::CONTATO_ORGAO_PUBLICO;
|
||||
}
|
||||
|
||||
public static function isValid(string $value): bool {
|
||||
return array_key_exists(key: $value, array: self::CONTATO_ORGAO_PUBLICO);
|
||||
}
|
||||
|
||||
public static function getDescription(string $key): ?string {
|
||||
return self::CONTATO_ORGAO_PUBLICO[$key] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Constants;
|
||||
|
||||
class PersonalidadeConst {
|
||||
public const CONTATO_PERSONALIDADE = [
|
||||
'PF' => 'Pessoa Física',
|
||||
'PJ' => 'Pessoa Jurídica',
|
||||
'EX' => 'Estrangeiro'
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::CONTATO_PERSONALIDADE;
|
||||
}
|
||||
|
||||
public static function isValid(string $value): bool {
|
||||
return array_key_exists(key: $value, array: self::CONTATO_PERSONALIDADE);
|
||||
}
|
||||
|
||||
public static function getDescription(string $key): ?string {
|
||||
return self::CONTATO_PERSONALIDADE[$key] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Constants;
|
||||
|
||||
class TipoConst {
|
||||
public const CONTATO_TIPO = [
|
||||
'CLIENTE' => 'Cliente',
|
||||
'FORNECEDOR' => 'Fornecedor'
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::CONTATO_TIPO;
|
||||
}
|
||||
|
||||
public static function isValid(string $value): bool {
|
||||
return array_key_exists(key: $value, array: self::CONTATO_TIPO);
|
||||
}
|
||||
|
||||
public static function getDescription(string $key): ?string {
|
||||
return self::CONTATO_TIPO[$key] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Controllers;
|
||||
|
||||
use KrothiumAPI\Utils\HttpUtil;
|
||||
use WorkbloomERP\Utils\SanitizeUtil;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
use WorkbloomERP\Module\v0\Contato\DTOs\ContatoCreateDTO;
|
||||
use WorkbloomERP\Module\v0\Contato\Factories\ContatoServiceFactory;
|
||||
|
||||
class ContatoController {
|
||||
public function createOptions(): void {
|
||||
try {
|
||||
$contatoService = ContatoServiceFactory::makeContatoService();
|
||||
$options = $contatoService->createOptions();
|
||||
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: 200,
|
||||
message: 'Opções para criação de contato.',
|
||||
output: ['options' => $options]
|
||||
);
|
||||
} catch (AppException $e) {
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $e->getCode() ?? 500,
|
||||
message: $e->getMessage() ?? 'Ocorreu um erro ao obter as opções de contato.',
|
||||
output: $e->getDetails() ? ['errors' => $e->getDetails()] : null
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Endpoint de API para a criação de um novo **Contato**.
|
||||
*
|
||||
* Este controlador atua como o ponto de entrada para o registro de novos contatos
|
||||
* (parceiros de negócio, fornecedores ou clientes) no sistema. O método é
|
||||
* responsável por extrair os dados da requisição HTTP (POST), aplicar a sanitização
|
||||
* necessária para garantir a integridade dos dados e delegar a criação à camada de
|
||||
* serviço utilizando um `ContatoCreateDTO`.
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Execução
|
||||
* 1. **Captura e Validação:** Obtém o corpo da requisição (JSON/Form). Valida a presença de dados, retornando erro 422 caso esteja vazio.
|
||||
* 2. **Normalização (DTO):** Instancia um `ContatoCreateDTO` populando-o com os dados sanitizados através de `SanitizeUtil`.
|
||||
* 3. **Orquestração de Serviço:** Invoca `ContatoService::create()` para validar regras de negócio, persistência e possíveis integrações.
|
||||
* 4. **Resposta:** Retorna um JSON estruturado com o status da operação (201 para sucesso ou status de erro correspondente).
|
||||
*
|
||||
* ---
|
||||
* ## Observações Técnicas
|
||||
* - O controlador segue o padrão de "Thin Controller", garantindo que as regras de validação complexas e a persistência de dados ocorram na camada de serviço.
|
||||
* - Todos os campos de entrada são submetidos a métodos de `SanitizeUtil` para prevenir injeções e garantir o tipo esperado antes de atingir o DTO.
|
||||
* - Utiliza `ContatoServiceFactory` para a injeção de dependência do serviço.
|
||||
*
|
||||
* @return void O método encerra o processamento enviando uma resposta HTTP JSON ao cliente.
|
||||
* @see ContatoCreateDTO Para a estrutura de dados esperada para a criação.
|
||||
* @see ContatoService::create() Para a lógica de negócio de persistência do contato.
|
||||
*/
|
||||
public function create(): void {
|
||||
try {
|
||||
$form = HttpUtil::getRequestBody(form_type: 'POST');
|
||||
if (empty($form)) {
|
||||
throw new AppException(message: 'Requisição inválida.', code: 422);
|
||||
}
|
||||
|
||||
$contaoService = ContatoServiceFactory::makeContatoService();
|
||||
$response = $contaoService->create(
|
||||
contatoCreateDTO: new ContatoCreateDTO(
|
||||
is_active: SanitizeUtil::boolean(value: $form['is_active'] ?? null),
|
||||
tipo: SanitizeUtil::string(value: $form['tipo'] ?? null),
|
||||
nome_empresarial: SanitizeUtil::string(value: $form['nome_empresarial'] ?? null),
|
||||
nome_fantasia: SanitizeUtil::string(value: $form['nome_fantasia'] ?? null),
|
||||
personalidade: SanitizeUtil::string(value: $form['personalidade'] ?? null),
|
||||
document_cpf: SanitizeUtil::string(value: $form['document_cpf'] ?? null),
|
||||
document_cnpj: SanitizeUtil::string(value: $form['document_cnpj'] ?? null),
|
||||
regime_tributario: SanitizeUtil::int(value: $form['regime_tributario'] ?? null),
|
||||
contribuinte_icms: SanitizeUtil::int(value: $form['contribuinte_icms'] ?? null),
|
||||
orgao_publico: SanitizeUtil::string(value: $form['orgao_publico'] ?? null),
|
||||
document_ie: SanitizeUtil::string(value: $form['document_ie'] ?? null),
|
||||
document_im: SanitizeUtil::string(value: $form['document_im'] ?? null),
|
||||
document_is: SanitizeUtil::string(value: $form['document_is'] ?? null),
|
||||
end_cep: SanitizeUtil::string(value: $form['end_cep'] ?? null),
|
||||
end_ibge: SanitizeUtil::string(value: $form['end_ibge'] ?? null),
|
||||
end_logradouro: SanitizeUtil::string(value: $form['end_logradouro'] ?? null),
|
||||
end_numero: SanitizeUtil::string(value: $form['end_numero'] ?? null),
|
||||
end_complemento: SanitizeUtil::string(value: $form['end_complemento'] ?? null),
|
||||
end_bairro: SanitizeUtil::string(value: $form['end_bairro'] ?? null),
|
||||
end_cidade: SanitizeUtil::string(value: $form['end_cidade'] ?? null),
|
||||
end_uf: SanitizeUtil::string(value: $form['end_uf'] ?? null),
|
||||
info_email: SanitizeUtil::string(value: $form['info_email'] ?? null),
|
||||
info_email_nfe: SanitizeUtil::string(value: $form['info_email_nfe'] ?? null),
|
||||
info_observacao: SanitizeUtil::string(value: $form['info_observacao'] ?? null),
|
||||
info_telefone: SanitizeUtil::string(value: $form['info_telefone'] ?? null),
|
||||
info_uso_consumo_ibs_cbs: SanitizeUtil::string(value: $form['info_uso_consumo_ibs_cbs'] ?? null),
|
||||
)
|
||||
);
|
||||
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $response['response_code'] ?? 201,
|
||||
message: $response['message'] ?? 'Contato criado com sucesso.',
|
||||
output: $response['output'] ?? null
|
||||
);
|
||||
} catch (AppException $e) {
|
||||
HttpUtil::jsonResponse(
|
||||
response_code: $e->getCode() ?? 500,
|
||||
message: $e->getMessage() ?? 'Ocorreu um erro ao criar o contato.',
|
||||
output: $e->getDetails() ? ['errors' => $e->getDetails()] : null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\DTOs;
|
||||
|
||||
class ContatoCreateDTO {
|
||||
public function __construct(
|
||||
private ?int $is_active = null,
|
||||
private ?string $tipo = null,
|
||||
private ?string $nome_empresarial = null,
|
||||
private ?string $nome_fantasia = null,
|
||||
private ?string $personalidade = null,
|
||||
private ?string $document_cpf = null,
|
||||
private ?string $document_cnpj = null,
|
||||
private ?int $regime_tributario = null,
|
||||
private ?int $contribuinte_icms = null,
|
||||
private ?string $orgao_publico = null,
|
||||
private ?string $document_ie = null,
|
||||
private ?string $document_im = null,
|
||||
private ?string $document_is = null,
|
||||
private ?string $end_cep = null,
|
||||
private ?string $end_ibge = null,
|
||||
private ?string $end_logradouro = null,
|
||||
private ?string $end_numero = null,
|
||||
private ?string $end_complemento = null,
|
||||
private ?string $end_bairro = null,
|
||||
private ?string $end_cidade = null,
|
||||
private ?string $end_uf = null,
|
||||
private ?string $info_email = null,
|
||||
private ?string $info_email_nfe = null,
|
||||
private ?string $info_observacao = null,
|
||||
private ?string $info_telefone = null,
|
||||
private ?string $info_uso_consumo_ibs_cbs = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'is_active' => $this->getIsActive(),
|
||||
'tipo' => $this->getTipo(),
|
||||
'nome_empresarial' => $this->getNomeEmpresarial(),
|
||||
'nome_fantasia' => $this->getNomeFantasia(),
|
||||
'personalidade' => $this->getPersonalidade(),
|
||||
'document_cpf' => $this->getDocumentCpf(),
|
||||
'document_cnpj' => $this->getDocumentCnpj(),
|
||||
'regime_tributario' => $this->getRegimeTributario(),
|
||||
'contribuinte_icms' => $this->getContribuinteIcms(),
|
||||
'orgao_publico' => $this->getOrgaoPublico(),
|
||||
'document_ie' => $this->getDocumentIe(),
|
||||
'document_im' => $this->getDocumentIm(),
|
||||
'document_is' => $this->getDocumentIs(),
|
||||
'end_cep' => $this->getEndCep(),
|
||||
'end_ibge' => $this->getEndIbge(),
|
||||
'end_logradouro' => $this->getEndLogradouro(),
|
||||
'end_numero' => $this->getEndNumero(),
|
||||
'end_complemento' => $this->getEndComplemento(),
|
||||
'end_bairro' => $this->getEndBairro(),
|
||||
'end_cidade' => $this->getEndCidade(),
|
||||
'end_uf' => $this->getEndUf(),
|
||||
'info_email' => $this->getInfoEmail(),
|
||||
'info_email_nfe' => $this->getInfoEmailNfe(),
|
||||
'info_observacao' => $this->getInfoObservacao(),
|
||||
'info_telefone' => $this->getInfoTelefone(),
|
||||
'info_uso_consumo_ibs_cbs' => $this->getInfoUsoConsumoIbsCbs(),
|
||||
];
|
||||
}
|
||||
|
||||
public function setIsActive(?int $is_active): void {
|
||||
$this->is_active = $is_active;
|
||||
}
|
||||
public function getIsActive(): ?int {
|
||||
return $this->is_active;
|
||||
}
|
||||
|
||||
public function setTipo(?string $tipo): void {
|
||||
$this->tipo = $tipo;
|
||||
}
|
||||
public function getTipo(): ?string {
|
||||
return $this->tipo;
|
||||
}
|
||||
|
||||
public function setNomeEmpresarial(?string $nome_empresarial): void {
|
||||
$this->nome_empresarial = $nome_empresarial;
|
||||
}
|
||||
public function getNomeEmpresarial(): ?string {
|
||||
return $this->nome_empresarial;
|
||||
}
|
||||
|
||||
public function setNomeFantasia(?string $nome_fantasia): void {
|
||||
$this->nome_fantasia = $nome_fantasia;
|
||||
}
|
||||
public function getNomeFantasia(): ?string {
|
||||
return $this->nome_fantasia;
|
||||
}
|
||||
|
||||
public function setPersonalidade(?string $personalidade): void {
|
||||
$this->personalidade = $personalidade;
|
||||
}
|
||||
public function getPersonalidade(): ?string {
|
||||
return $this->personalidade;
|
||||
}
|
||||
|
||||
public function setDocumentCpf(?string $document_cpf): void {
|
||||
$this->document_cpf = $document_cpf;
|
||||
}
|
||||
public function getDocumentCpf(): ?string {
|
||||
return $this->document_cpf;
|
||||
}
|
||||
|
||||
public function setDocumentCnpj(?string $document_cnpj): void {
|
||||
$this->document_cnpj = $document_cnpj;
|
||||
}
|
||||
public function getDocumentCnpj(): ?string {
|
||||
return $this->document_cnpj;
|
||||
}
|
||||
|
||||
public function setRegimeTributario(?int $regime_tributario): void {
|
||||
$this->regime_tributario = $regime_tributario;
|
||||
}
|
||||
public function getRegimeTributario(): ?int {
|
||||
return $this->regime_tributario;
|
||||
}
|
||||
|
||||
public function setContribuinteIcms(?int $contribuinte_icms): void {
|
||||
$this->contribuinte_icms = $contribuinte_icms;
|
||||
}
|
||||
public function getContribuinteIcms(): ?int {
|
||||
return $this->contribuinte_icms;
|
||||
}
|
||||
|
||||
public function setOrgaoPublico(?string $orgao_publico): void {
|
||||
$this->orgao_publico = $orgao_publico;
|
||||
}
|
||||
public function getOrgaoPublico(): ?string {
|
||||
return $this->orgao_publico;
|
||||
}
|
||||
|
||||
public function setDocumentIe(?string $document_ie): void {
|
||||
$this->document_ie = $document_ie;
|
||||
}
|
||||
public function getDocumentIe(): ?string {
|
||||
return $this->document_ie;
|
||||
}
|
||||
|
||||
public function setDocumentIm(?string $document_im): void {
|
||||
$this->document_im = $document_im;
|
||||
}
|
||||
public function getDocumentIm(): ?string {
|
||||
return $this->document_im;
|
||||
}
|
||||
|
||||
public function setDocumentIs(?string $document_is): void {
|
||||
$this->document_is = $document_is;
|
||||
}
|
||||
public function getDocumentIs(): ?string {
|
||||
return $this->document_is;
|
||||
}
|
||||
|
||||
public function setEndCep(?string $end_cep): void {
|
||||
$this->end_cep = $end_cep;
|
||||
}
|
||||
public function getEndCep(): ?string {
|
||||
return $this->end_cep;
|
||||
}
|
||||
|
||||
public function setEndIbge(?string $end_ibge): void {
|
||||
$this->end_ibge = $end_ibge;
|
||||
}
|
||||
public function getEndIbge(): ?string {
|
||||
return $this->end_ibge;
|
||||
}
|
||||
|
||||
public function setEndLogradouro(?string $end_logradouro): void {
|
||||
$this->end_logradouro = $end_logradouro;
|
||||
}
|
||||
public function getEndLogradouro(): ?string {
|
||||
return $this->end_logradouro;
|
||||
}
|
||||
|
||||
public function setEndNumero(?string $end_numero): void {
|
||||
$this->end_numero = $end_numero;
|
||||
}
|
||||
public function getEndNumero(): ?string {
|
||||
return $this->end_numero;
|
||||
}
|
||||
|
||||
public function setEndComplemento(?string $end_complemento): void {
|
||||
$this->end_complemento = $end_complemento;
|
||||
}
|
||||
public function getEndComplemento(): ?string {
|
||||
return $this->end_complemento;
|
||||
}
|
||||
|
||||
public function setEndBairro(?string $end_bairro): void {
|
||||
$this->end_bairro = $end_bairro;
|
||||
}
|
||||
public function getEndBairro(): ?string {
|
||||
return $this->end_bairro;
|
||||
}
|
||||
|
||||
public function setEndCidade(?string $end_cidade): void {
|
||||
$this->end_cidade = $end_cidade;
|
||||
}
|
||||
public function getEndCidade(): ?string {
|
||||
return $this->end_cidade;
|
||||
}
|
||||
|
||||
public function setEndUf(?string $end_uf): void {
|
||||
$this->end_uf = $end_uf;
|
||||
}
|
||||
public function getEndUf(): ?string {
|
||||
return $this->end_uf;
|
||||
}
|
||||
|
||||
public function setInfoEmail(?string $info_email): void {
|
||||
$this->info_email = $info_email;
|
||||
}
|
||||
public function getInfoEmail(): ?string {
|
||||
return $this->info_email;
|
||||
}
|
||||
|
||||
public function setInfoEmailNfe(?string $info_email_nfe): void {
|
||||
$this->info_email_nfe = $info_email_nfe;
|
||||
}
|
||||
public function getInfoEmailNfe(): ?string {
|
||||
return $this->info_email_nfe;
|
||||
}
|
||||
|
||||
public function setInfoObservacao(?string $info_observacao): void {
|
||||
$this->info_observacao = $info_observacao;
|
||||
}
|
||||
public function getInfoObservacao(): ?string {
|
||||
return $this->info_observacao;
|
||||
}
|
||||
|
||||
public function setInfoTelefone(?string $info_telefone): void {
|
||||
$this->info_telefone = $info_telefone;
|
||||
}
|
||||
public function getInfoTelefone(): ?string {
|
||||
return $this->info_telefone;
|
||||
}
|
||||
|
||||
public function setInfoUsoConsumoIbsCbs(?string $info_uso_consumo_ibs_cbs): void {
|
||||
$this->info_uso_consumo_ibs_cbs = $info_uso_consumo_ibs_cbs;
|
||||
}
|
||||
public function getInfoUsoConsumoIbsCbs(): ?string {
|
||||
return $this->info_uso_consumo_ibs_cbs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Factories;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Utils\ValidateUtil;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
use WorkbloomERP\Module\v0\Empresa\Repos\EmpresaRepo;
|
||||
use WorkbloomERP\Module\v0\Contato\Repos\ContatoRepo;
|
||||
use WorkbloomERP\Module\v0\Contato\Models\ContatoModel;
|
||||
use WorkbloomERP\Module\v0\Empresa\Models\EmpresaModel;
|
||||
use WorkbloomERP\Module\v0\Contato\DTOs\ContatoCreateDTO;
|
||||
|
||||
// Constantes para opções de contato
|
||||
use WorkbloomERP\Constants\SpedCRTConst;
|
||||
use WorkbloomERP\Constants\BrasilUfsConst;
|
||||
use WorkbloomERP\Constants\SpedPaisesConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\OrgaoPublicoConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\PersonalidadeConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\ContribuinteICMSConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\NFSeConsumoIbsCbsConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\TipoConst;
|
||||
|
||||
class ContatoFactory {
|
||||
public function __construct(private EmpresaModel $empresaModel, private ContatoCreateDTO $contatoCreateDTO, private ContatoRepo $contatoRepo, private EmpresaRepo $empresaRepo, private DBService $db) {
|
||||
if (!$empresaModel) {
|
||||
throw new AppException(message: 'Empresa não encontrada.', code: 404);
|
||||
}
|
||||
if (!$contatoCreateDTO) {
|
||||
throw new AppException(message: 'Dados do contato inválidos.', code: 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function create(ContatoCreateDTO $contatoCreateDTO, EmpresaModel $empresaModel): ?ContatoModel {
|
||||
try {
|
||||
// Valida os campos comuns a ambos os tipos de contato (PF e PJ)
|
||||
$this->validateCommonFields(contatoCreateDTO: $contatoCreateDTO);
|
||||
|
||||
// Cria o contato com base na personalidade (PF ou PJ)
|
||||
$contatoModel = match($contatoCreateDTO->getPersonalidade()) {
|
||||
'PF' => $this->createPF(contatoCreateDTO: $contatoCreateDTO, empresaModel: $empresaModel),
|
||||
'PJ' => $this->createPJ(contatoCreateDTO: $contatoCreateDTO, empresaModel: $empresaModel)
|
||||
};
|
||||
|
||||
return $contatoModel;
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function validateCommonFields(ContatoCreateDTO $contatoCreateDTO): void {
|
||||
$errors = [];
|
||||
|
||||
$commonFields = [
|
||||
'is_active',
|
||||
'tipo',
|
||||
'nome_empresarial',
|
||||
'personalidade',
|
||||
'regime_tributario',
|
||||
'contribuinte_icms',
|
||||
'orgao_publico',
|
||||
'end_cep',
|
||||
'end_logradouro',
|
||||
'end_numero',
|
||||
'end_bairro',
|
||||
'end_cidade',
|
||||
'end_uf',
|
||||
'info_email',
|
||||
'info_email_nfe',
|
||||
'info_telefone',
|
||||
'info_uso_consumo_ibs_cbs',
|
||||
];
|
||||
|
||||
foreach ($commonFields as $field) {
|
||||
if (!array_key_exists(key: $field, array: $contatoCreateDTO->toArray()) || $contatoCreateDTO->toArray()[$field] === '') {
|
||||
$errors[$field] = 'Campo obrigatório.';
|
||||
}
|
||||
}
|
||||
|
||||
// Verifica se o campo 'tipo' tem um valor válido
|
||||
$tipo = $contatoCreateDTO->getTipo() ? strtoupper(string: $contatoCreateDTO->getTipo()) : null;
|
||||
if ($tipo && !TipoConst::isValid(value: $tipo)) {
|
||||
$errors['tipo'] = "Valor inválido.";
|
||||
}
|
||||
|
||||
// Verifica se o campo 'personalidade' tem um valor válido
|
||||
$personalidade = $contatoCreateDTO->getPersonalidade() ? strtoupper(string: $contatoCreateDTO->getPersonalidade()) : null;
|
||||
if ($personalidade && !PersonalidadeConst::isValid(value: $personalidade)) {
|
||||
$errors['personalidade'] = "Valor inválido.";
|
||||
}
|
||||
|
||||
// Valida o regime tributário
|
||||
if (!in_array($contatoCreateDTO->getRegimeTributario(), [1, 2, 3, 4, null], true)) {
|
||||
$errors['regime_tributario'] = "Valor inválido.";
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new AppException(message: 'Dados de contato inválidos.',code: 422, details: $errors);
|
||||
}
|
||||
}
|
||||
|
||||
private function createPF(ContatoCreateDTO $contatoCreateDTO, EmpresaModel $empresaModel): ContatoModel {
|
||||
try {
|
||||
// Valida os campos específicos para pessoa física
|
||||
$this->validatePFFields(contatoCreateDTO: $contatoCreateDTO, empresaModel: $empresaModel);
|
||||
|
||||
$contatoModel = new ContatoModel();
|
||||
$contatoModel->setUuid(Uuid::uuid7()->toString());
|
||||
$contatoModel->setEmpresaId($empresaModel->getId());
|
||||
$contatoModel->setIsActive($contatoCreateDTO->getIsActive());
|
||||
$contatoModel->setTipo($contatoCreateDTO->getTipo());
|
||||
$contatoModel->setNomeEmpresarial($contatoCreateDTO->getNomeEmpresarial());
|
||||
$contatoModel->setPersonalidade($contatoCreateDTO->getPersonalidade());
|
||||
$contatoModel->setRegimeTributario($contatoCreateDTO->getRegimeTributario());
|
||||
$contatoModel->setContribuinteIcms($contatoCreateDTO->getContribuinteIcms());
|
||||
$contatoModel->setOrgaoPublico($contatoCreateDTO->getOrgaoPublico());
|
||||
$contatoModel->setEndCep($contatoCreateDTO->getEndCep());
|
||||
$contatoModel->setEndIbge($contatoCreateDTO->getEndIbge());
|
||||
$contatoModel->setEndLogradouro($contatoCreateDTO->getEndLogradouro());
|
||||
$contatoModel->setEndNumero($contatoCreateDTO->getEndNumero());
|
||||
$contatoModel->setEndBairro($contatoCreateDTO->getEndBairro());
|
||||
$contatoModel->setEndCidade($contatoCreateDTO->getEndCidade());
|
||||
$contatoModel->setEndUf($contatoCreateDTO->getEndUf());
|
||||
$contatoModel->setInfoEmail($contatoCreateDTO->getInfoEmail());
|
||||
$contatoModel->setInfoEmailNfe($contatoCreateDTO->getInfoEmailNfe());
|
||||
$contatoModel->setInfoObservacao($contatoCreateDTO->getInfoObservacao());
|
||||
$contatoModel->setInfoTelefone($contatoCreateDTO->getInfoTelefone());
|
||||
$contatoModel->setInfoUsoConsumoIbsCbs($contatoCreateDTO->getInfoUsoConsumoIbsCbs());
|
||||
$contatoModel->setCreatedAt(new DateTimeImmutable());
|
||||
|
||||
return $contatoModel;
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function validatePFFields(ContatoCreateDTO $contatoCreateDTO, EmpresaModel $empresaModel): void {
|
||||
$errors = [];
|
||||
|
||||
if (($contatoCreateDTO->getDocumentCpf() === '') || !ValidateUtil::cpf(cpf: $contatoCreateDTO->getDocumentCpf())) {
|
||||
$errors['document_cpf'] = 'CPF informado é inválido.';
|
||||
throw new AppException(message: 'Dados de contato inválidos.', code: 422, details: $errors);
|
||||
}
|
||||
|
||||
$existingContato = $this->contatoRepo->findOneByConditions(
|
||||
empresa_id: $empresaModel->getId(),
|
||||
conditions: [
|
||||
['field' => 'document_cpf', 'operator' => '=', 'value' => $contatoCreateDTO->getDocumentCpf()]
|
||||
]
|
||||
);
|
||||
|
||||
if ($existingContato) {
|
||||
$errors['document_cpf'] = 'CPF já cadastrado para esta empresa.';
|
||||
throw new AppException(message: 'Dados de contato inválidos.', code: 422, details: $errors);
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new AppException(message: 'Dados de contato inválidos.', code: 422, details: $errors);
|
||||
}
|
||||
}
|
||||
|
||||
private function createPJ(ContatoCreateDTO $contatoCreateDTO, EmpresaModel $empresaModel): ContatoModel {
|
||||
try {
|
||||
// Valida os campos específicos para pessoa física
|
||||
// $this->validatePFFields(contatoCreateDTO: $contatoCreateDTO);
|
||||
|
||||
return new ContatoModel();
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Factories;
|
||||
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Empresa\Repos\EmpresaRepo;
|
||||
use WorkbloomERP\Module\v0\Contato\Repos\ContatoRepo;
|
||||
use WorkbloomERP\Module\v0\Empresa\Models\EmpresaModel;
|
||||
use WorkbloomERP\Module\v0\Contato\DTOs\ContatoCreateDTO;
|
||||
use WorkbloomERP\Module\v0\Contato\Services\ContatoService;
|
||||
|
||||
class ContatoServiceFactory {
|
||||
public static function makeContatoService(): ContatoService {
|
||||
$db = new DBService();
|
||||
return new ContatoService(
|
||||
db: $db,
|
||||
contatoRepo: new ContatoRepo(db: $db),
|
||||
empresaRepo: new EmpresaRepo(db: $db),
|
||||
);
|
||||
}
|
||||
|
||||
public static function makeContratoFactory(EmpresaModel $empresaModel, ContatoCreateDTO $contatoCreateDTO): ContatoFactory {
|
||||
$db = new DBService();
|
||||
return new ContatoFactory(
|
||||
db: $db,
|
||||
empresaModel: $empresaModel,
|
||||
contatoCreateDTO: $contatoCreateDTO,
|
||||
contatoRepo: new ContatoRepo(db: $db),
|
||||
empresaRepo: new EmpresaRepo(db: $db),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
|
||||
class ContatoModel {
|
||||
public function __construct(
|
||||
private ?int $id = null,
|
||||
private ?string $uuid = null,
|
||||
private ?int $empresa_id = null,
|
||||
private ?bool $is_active = null,
|
||||
private ?string $tipo = null,
|
||||
private ?string $nome_empresarial = null,
|
||||
private ?string $nome_fantasia = null,
|
||||
private ?string $personalidade = null,
|
||||
private ?string $document_cpf = null,
|
||||
private ?string $document_cnpj = null,
|
||||
private ?int $regime_tributario = null,
|
||||
private ?int $contribuinte_icms = null,
|
||||
private ?string $orgao_publico = null,
|
||||
private ?string $document_ie = null,
|
||||
private ?string $document_im = null,
|
||||
private ?string $document_is = null,
|
||||
private ?string $end_pais = null,
|
||||
private ?string $end_cep = null,
|
||||
private ?string $end_ibge = null,
|
||||
private ?string $end_logradouro = null,
|
||||
private ?string $end_numero = null,
|
||||
private ?string $end_complemento = null,
|
||||
private ?string $end_bairro = null,
|
||||
private ?string $end_cidade = null,
|
||||
private ?string $end_uf = null,
|
||||
private ?string $info_email = null,
|
||||
private ?string $info_email_nfe = null,
|
||||
private ?string $info_observacao = null,
|
||||
private ?string $info_telefone = null,
|
||||
private ?int $info_uso_consumo_ibs_cbs = null,
|
||||
private ?DateTimeImmutable $created_at = null,
|
||||
private ?DateTimeImmutable $updated_at = null,
|
||||
private ?DateTimeImmutable $deleted_at = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'uuid' => $this->getUuid(),
|
||||
'empresa_id' => $this->getEmpresaId(),
|
||||
'is_active' => $this->getIsActive(),
|
||||
'tipo' => $this->getTipo(),
|
||||
'nome_empresarial' => $this->getNomeEmpresarial(),
|
||||
'nome_fantasia' => $this->getNomeFantasia(),
|
||||
'personalidade' => $this->getPersonalidade(),
|
||||
'document_cpf' => $this->getDocumentCpf(),
|
||||
'document_cnpj' => $this->getDocumentCnpj(),
|
||||
'regime_tributario' => $this->getRegimeTributario(),
|
||||
'contribuinte_icms' => $this->getContribuinteIcms(),
|
||||
'orgao_publico' => $this->getOrgaoPublico(),
|
||||
'document_ie' => $this->getDocumentIe(),
|
||||
'document_im' => $this->getDocumentIm(),
|
||||
'document_is' => $this->getDocumentIs(),
|
||||
'end_pais' => $this->getEndPais(),
|
||||
'end_cep' => $this->getEndCep(),
|
||||
'end_ibge' => $this->getEndIbge(),
|
||||
'end_logradouro' => $this->getEndLogradouro(),
|
||||
'end_numero' => $this->getEndNumero(),
|
||||
'end_complemento' => $this->getEndComplemento(),
|
||||
'end_bairro' => $this->getEndBairro(),
|
||||
'end_cidade' => $this->getEndCidade(),
|
||||
'end_uf' => $this->getEndUf(),
|
||||
'info_email' => $this->getInfoEmail(),
|
||||
'info_email_nfe' => $this->getInfoEmailNfe(),
|
||||
'info_observacao' => $this->getInfoObservacao(),
|
||||
'info_telefone' => $this->getInfoTelefone(),
|
||||
'info_uso_consumo_ibs_cbs' => $this->getInfoUsoConsumoIbsCbs(),
|
||||
'created_at' => $this->getCreatedAt() ? $this->getCreatedAt()->format(format: 'Y-m-d H:i:s') : null,
|
||||
'updated_at' => $this->getUpdatedAt() ? $this->getUpdatedAt()->format(format: 'Y-m-d H:i:s') : null,
|
||||
'deleted_at' => $this->getDeletedAt() ? $this->getDeletedAt()->format(format: 'Y-m-d H:i:s') : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function setId(?int $id): void {
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId(): ?int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setUuid(?string $uuid): void {
|
||||
$this->uuid = $uuid;
|
||||
}
|
||||
public function getUuid(): ?string {
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function setEmpresaId(?int $empresa_id): void {
|
||||
$this->empresa_id = $empresa_id;
|
||||
}
|
||||
public function getEmpresaId(): ?int {
|
||||
return $this->empresa_id;
|
||||
}
|
||||
|
||||
public function setIsActive(?bool $is_active): void {
|
||||
$this->is_active = $is_active;
|
||||
}
|
||||
public function getIsActive(): ?bool {
|
||||
return $this->is_active;
|
||||
}
|
||||
|
||||
public function setTipo(?string $tipo): void {
|
||||
$this->tipo = $tipo;
|
||||
}
|
||||
public function getTipo(): ?string {
|
||||
return $this->tipo;
|
||||
}
|
||||
|
||||
public function setNomeEmpresarial(?string $nome_empresarial): void {
|
||||
$this->nome_empresarial = $nome_empresarial;
|
||||
}
|
||||
public function getNomeEmpresarial(): ?string {
|
||||
return $this->nome_empresarial;
|
||||
}
|
||||
|
||||
public function setNomeFantasia(?string $nome_fantasia): void {
|
||||
$this->nome_fantasia = $nome_fantasia;
|
||||
}
|
||||
public function getNomeFantasia(): ?string {
|
||||
return $this->nome_fantasia;
|
||||
}
|
||||
|
||||
public function setPersonalidade(?string $personalidade): void {
|
||||
$this->personalidade = $personalidade;
|
||||
}
|
||||
public function getPersonalidade(): ?string {
|
||||
return $this->personalidade;
|
||||
}
|
||||
|
||||
public function setDocumentCpf(?string $document_cpf): void {
|
||||
$this->document_cpf = $document_cpf;
|
||||
}
|
||||
public function getDocumentCpf(): ?string {
|
||||
return $this->document_cpf;
|
||||
}
|
||||
|
||||
public function setDocumentCnpj(?string $document_cnpj): void {
|
||||
$this->document_cnpj = $document_cnpj;
|
||||
}
|
||||
public function getDocumentCnpj(): ?string {
|
||||
return $this->document_cnpj;
|
||||
}
|
||||
|
||||
public function setRegimeTributario(?int $regime_tributario): void {
|
||||
$this->regime_tributario = $regime_tributario;
|
||||
}
|
||||
public function getRegimeTributario(): ?int {
|
||||
return $this->regime_tributario;
|
||||
}
|
||||
|
||||
public function setContribuinteIcms(?int $contribuinte_icms): void {
|
||||
$this->contribuinte_icms = $contribuinte_icms;
|
||||
}
|
||||
public function getContribuinteIcms(): ?int {
|
||||
return $this->contribuinte_icms;
|
||||
}
|
||||
|
||||
public function setOrgaoPublico(?string $orgao_publico): void {
|
||||
$this->orgao_publico = $orgao_publico;
|
||||
}
|
||||
public function getOrgaoPublico(): ?string {
|
||||
return $this->orgao_publico;
|
||||
}
|
||||
|
||||
public function setDocumentIe(?string $document_ie): void {
|
||||
$this->document_ie = $document_ie;
|
||||
}
|
||||
public function getDocumentIe(): ?string {
|
||||
return $this->document_ie;
|
||||
}
|
||||
|
||||
public function setDocumentIm(?string $document_im): void {
|
||||
$this->document_im = $document_im;
|
||||
}
|
||||
public function getDocumentIm(): ?string {
|
||||
return $this->document_im;
|
||||
}
|
||||
|
||||
public function setDocumentIs(?string $document_is): void {
|
||||
$this->document_is = $document_is;
|
||||
}
|
||||
public function getDocumentIs(): ?string {
|
||||
return $this->document_is;
|
||||
}
|
||||
|
||||
public function setEndPais(?string $end_pais): void {
|
||||
$this->end_pais = $end_pais;
|
||||
}
|
||||
public function getEndPais(): ?string {
|
||||
return $this->end_pais;
|
||||
}
|
||||
|
||||
public function setEndCep(?string $end_cep): void {
|
||||
$this->end_cep = $end_cep;
|
||||
}
|
||||
public function getEndCep(): ?string {
|
||||
return $this->end_cep;
|
||||
}
|
||||
|
||||
public function setEndIbge(?string $end_ibge): void {
|
||||
$this->end_ibge = $end_ibge;
|
||||
}
|
||||
public function getEndIbge(): ?string {
|
||||
return $this->end_ibge;
|
||||
}
|
||||
|
||||
public function setEndLogradouro(?string $end_logradouro): void {
|
||||
$this->end_logradouro = $end_logradouro;
|
||||
}
|
||||
public function getEndLogradouro(): ?string {
|
||||
return $this->end_logradouro;
|
||||
}
|
||||
|
||||
public function setEndNumero(?string $end_numero): void {
|
||||
$this->end_numero = $end_numero;
|
||||
}
|
||||
public function getEndNumero(): ?string {
|
||||
return $this->end_numero;
|
||||
}
|
||||
|
||||
public function setEndComplemento(?string $end_complemento): void {
|
||||
$this->end_complemento = $end_complemento;
|
||||
}
|
||||
public function getEndComplemento(): ?string {
|
||||
return $this->end_complemento;
|
||||
}
|
||||
|
||||
public function setEndBairro(?string $end_bairro): void {
|
||||
$this->end_bairro = $end_bairro;
|
||||
}
|
||||
public function getEndBairro(): ?string {
|
||||
return $this->end_bairro;
|
||||
}
|
||||
|
||||
public function setEndCidade(?string $end_cidade): void {
|
||||
$this->end_cidade = $end_cidade;
|
||||
}
|
||||
public function getEndCidade(): ?string {
|
||||
return $this->end_cidade;
|
||||
}
|
||||
|
||||
public function setEndUf(?string $end_uf): void {
|
||||
$this->end_uf = $end_uf;
|
||||
}
|
||||
public function getEndUf(): ?string {
|
||||
return $this->end_uf;
|
||||
}
|
||||
|
||||
public function setInfoEmail(?string $info_email): void {
|
||||
$this->info_email = $info_email;
|
||||
}
|
||||
public function getInfoEmail(): ?string {
|
||||
return $this->info_email;
|
||||
}
|
||||
|
||||
public function setInfoEmailNfe(?string $info_email_nfe): void {
|
||||
$this->info_email_nfe = $info_email_nfe;
|
||||
}
|
||||
public function getInfoEmailNfe(): ?string {
|
||||
return $this->info_email_nfe;
|
||||
}
|
||||
|
||||
public function setInfoObservacao(?string $info_observacao): void {
|
||||
$this->info_observacao = $info_observacao;
|
||||
}
|
||||
public function getInfoObservacao(): ?string {
|
||||
return $this->info_observacao;
|
||||
}
|
||||
|
||||
public function setInfoTelefone(?string $info_telefone): void {
|
||||
$this->info_telefone = $info_telefone;
|
||||
}
|
||||
public function getInfoTelefone(): ?string {
|
||||
return $this->info_telefone;
|
||||
}
|
||||
|
||||
public function setInfoUsoConsumoIbsCbs(?int $info_uso_consumo_ibs_cbs): void {
|
||||
$this->info_uso_consumo_ibs_cbs = $info_uso_consumo_ibs_cbs;
|
||||
}
|
||||
public function getInfoUsoConsumoIbsCbs(): ?int {
|
||||
return $this->info_uso_consumo_ibs_cbs;
|
||||
}
|
||||
|
||||
public function setCreatedAt(?DateTimeImmutable $created_at): void {
|
||||
$this->created_at = $created_at;
|
||||
}
|
||||
public function getCreatedAt(): ?DateTimeImmutable {
|
||||
return $this->created_at;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeImmutable $updated_at): void {
|
||||
$this->updated_at = $updated_at;
|
||||
}
|
||||
public function getUpdatedAt(): ?DateTimeImmutable {
|
||||
return $this->updated_at;
|
||||
}
|
||||
|
||||
public function setDeletedAt(?DateTimeImmutable $deleted_at): void {
|
||||
$this->deleted_at = $deleted_at;
|
||||
}
|
||||
public function getDeletedAt(): ?DateTimeImmutable {
|
||||
return $this->deleted_at;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Repos;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Contato\Models\ContatoModel;
|
||||
|
||||
class ContatoRepo {
|
||||
protected string $contatoTable = 'contato';
|
||||
public function __construct(
|
||||
private DBService $db
|
||||
) {}
|
||||
|
||||
public function insert(ContatoModel $contatoModel): ContatoModel {
|
||||
$query =
|
||||
"INSERT INTO {$this->contatoTable} (
|
||||
uuid,
|
||||
empresa_id,
|
||||
is_active,
|
||||
tipo,
|
||||
nome_empresarial,
|
||||
nome_fantasia,
|
||||
personalidade,
|
||||
document_cpf,
|
||||
document_cnpj,
|
||||
regime_tributario,
|
||||
contribuinte_icms,
|
||||
orgao_publico,
|
||||
document_ie,
|
||||
document_im,
|
||||
document_is,
|
||||
end_pais,
|
||||
end_cep,
|
||||
end_ibge,
|
||||
end_logradouro,
|
||||
end_numero,
|
||||
end_complemento,
|
||||
end_bairro,
|
||||
end_cidade,
|
||||
end_uf,
|
||||
info_email,
|
||||
info_email_nfe,
|
||||
info_observacao,
|
||||
info_telefone,
|
||||
info_uso_consumo_ibs_cbs,
|
||||
created_at
|
||||
) VALUES (
|
||||
:uuid,
|
||||
:empresa_id,
|
||||
:is_active,
|
||||
:tipo,
|
||||
:nome_empresarial,
|
||||
:nome_fantasia,
|
||||
:personalidade,
|
||||
:document_cpf,
|
||||
:document_cnpj,
|
||||
:regime_tributario,
|
||||
:contribuinte_icms,
|
||||
:orgao_publico,
|
||||
:document_ie,
|
||||
:document_im,
|
||||
:document_is,
|
||||
:end_pais,
|
||||
:end_cep,
|
||||
:end_ibge,
|
||||
:end_logradouro,
|
||||
:end_numero,
|
||||
:end_complemento,
|
||||
:end_bairro,
|
||||
:end_cidade,
|
||||
:end_uf,
|
||||
:info_email,
|
||||
:info_email_nfe,
|
||||
:info_observacao,
|
||||
:info_telefone,
|
||||
:info_uso_consumo_ibs_cbs,
|
||||
:created_at
|
||||
)";
|
||||
|
||||
$contatoModel->setCreatedAt(new DateTimeImmutable());
|
||||
|
||||
$this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':uuid' => $contatoModel->getUuid(),
|
||||
':empresa_id' => $contatoModel->getEmpresaId(),
|
||||
':is_active' => $contatoModel->getIsActive(),
|
||||
':tipo' => $contatoModel->getTipo(),
|
||||
':nome_empresarial' => $contatoModel->getNomeEmpresarial(),
|
||||
':nome_fantasia' => $contatoModel->getNomeFantasia(),
|
||||
':personalidade' => $contatoModel->getPersonalidade(),
|
||||
':document_cpf' => $contatoModel->getDocumentCpf(),
|
||||
':document_cnpj' => $contatoModel->getDocumentCnpj(),
|
||||
':regime_tributario' => $contatoModel->getRegimeTributario(),
|
||||
':contribuinte_icms' => $contatoModel->getContribuinteIcms(),
|
||||
':orgao_publico' => $contatoModel->getOrgaoPublico(),
|
||||
':document_ie' => $contatoModel->getDocumentIe(),
|
||||
':document_im' => $contatoModel->getDocumentIm(),
|
||||
':document_is' => $contatoModel->getDocumentIs(),
|
||||
':end_pais' => $contatoModel->getEndPais(),
|
||||
':end_cep' => $contatoModel->getEndCep(),
|
||||
':end_ibge' => $contatoModel->getEndIbge(),
|
||||
':end_logradouro' => $contatoModel->getEndLogradouro(),
|
||||
':end_numero' => $contatoModel->getEndNumero(),
|
||||
':end_complemento' => $contatoModel->getEndComplemento(),
|
||||
':end_bairro' => $contatoModel->getEndBairro(),
|
||||
':end_cidade' => $contatoModel->getEndCidade(),
|
||||
':end_uf' => $contatoModel->getEndUf(),
|
||||
':info_email' => $contatoModel->getInfoEmail(),
|
||||
':info_email_nfe' => $contatoModel->getInfoEmailNfe(),
|
||||
':info_observacao' => $contatoModel->getInfoObservacao(),
|
||||
':info_telefone' => $contatoModel->getInfoTelefone(),
|
||||
':info_uso_consumo_ibs_cbs' => $contatoModel->getInfoUsoConsumoIbsCbs(),
|
||||
':created_at' => $contatoModel->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
|
||||
$contatoModel->setId(id: $this->db->lastInsertId());
|
||||
return $contatoModel;
|
||||
}
|
||||
|
||||
public function update(ContatoModel $contatoModel): bool {
|
||||
$query =
|
||||
"UPDATE {$this->contatoTable} SET
|
||||
is_active = :is_active,
|
||||
tipo = :tipo,
|
||||
nome_empresarial = :nome_empresarial,
|
||||
nome_fantasia = :nome_fantasia,
|
||||
personalidade = :personalidade,
|
||||
document_cpf = :document_cpf,
|
||||
document_cnpj = :document_cnpj,
|
||||
regime_tributario = :regime_tributario,
|
||||
contribuinte_icms = :contribuinte_icms,
|
||||
orgao_publico = :orgao_publico,
|
||||
document_ie = :document_ie,
|
||||
document_im = :document_im,
|
||||
document_is = :document_is,
|
||||
end_pais = :end_pais,
|
||||
end_cep = :end_cep,
|
||||
end_ibge = :end_ibge,
|
||||
end_logradouro = :end_logradouro,
|
||||
end_numero = :end_numero,
|
||||
end_complemento = :end_complemento,
|
||||
end_bairro = :end_bairro,
|
||||
end_cidade = :end_cidade,
|
||||
end_uf = :end_uf,
|
||||
info_email = :info_email,
|
||||
info_email_nfe = :info_email_nfe,
|
||||
info_observacao = :info_observacao,
|
||||
info_telefone = :info_telefone,
|
||||
info_uso_consumo_ibs_cbs = :info_uso_consumo_ibs_cbs,
|
||||
updated_at = :updated_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $contatoModel->getId(),
|
||||
':uuid' => $contatoModel->getUuid(),
|
||||
':empresa_id' => $contatoModel->getEmpresaId(),
|
||||
':is_active' => $contatoModel->getIsActive(),
|
||||
':tipo' => $contatoModel->getTipo(),
|
||||
':nome_empresarial' => $contatoModel->getNomeEmpresarial(),
|
||||
':nome_fantasia' => $contatoModel->getNomeFantasia(),
|
||||
':personalidade' => $contatoModel->getPersonalidade(),
|
||||
':document_cpf' => $contatoModel->getDocumentCpf(),
|
||||
':document_cnpj' => $contatoModel->getDocumentCnpj(),
|
||||
':regime_tributario' => $contatoModel->getRegimeTributario(),
|
||||
':contribuinte_icms' => $contatoModel->getContribuinteIcms(),
|
||||
':orgao_publico' => $contatoModel->getOrgaoPublico(),
|
||||
':document_ie' => $contatoModel->getDocumentIe(),
|
||||
':document_im' => $contatoModel->getDocumentIm(),
|
||||
':document_is' => $contatoModel->getDocumentIs(),
|
||||
':end_pais' => $contatoModel->getEndPais(),
|
||||
':end_cep' => $contatoModel->getEndCep(),
|
||||
':end_ibge' => $contatoModel->getEndIbge(),
|
||||
':end_logradouro' => $contatoModel->getEndLogradouro(),
|
||||
':end_numero' => $contatoModel->getEndNumero(),
|
||||
':end_complemento' => $contatoModel->getEndComplemento(),
|
||||
':end_bairro' => $contatoModel->getEndBairro(),
|
||||
':end_cidade' => $contatoModel->getEndCidade(),
|
||||
':end_uf' => $contatoModel->getEndUf(),
|
||||
':info_email' => $contatoModel->getInfoEmail(),
|
||||
':info_email_nfe' => $contatoModel->getInfoEmailNfe(),
|
||||
':info_observacao' => $contatoModel->getInfoObservacao(),
|
||||
':info_telefone' => $contatoModel->getInfoTelefone(),
|
||||
':info_uso_consumo_ibs_cbs' => $contatoModel->getInfoUsoConsumoIbsCbs(),
|
||||
':updated_at' => $contatoModel->getUpdatedAt()->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(ContatoModel $contatoModel): bool {
|
||||
$query =
|
||||
"UPDATE {$this->contatoTable} SET
|
||||
deleted_at = :deleted_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $contatoModel->getId(),
|
||||
':uuid' => $contatoModel->getUuid(),
|
||||
':deleted_at' => (new DateTimeImmutable())->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function findByIdentifier(int $empresa_id, string $identifier, mixed $value) {
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
empresa_id,
|
||||
is_active,
|
||||
nome_empresarial,
|
||||
nome_fantasia,
|
||||
personalidade,
|
||||
document_cpf,
|
||||
document_cnpj,
|
||||
regime_tributario,
|
||||
contribuinte_icms,
|
||||
orgao_publico,
|
||||
document_ie,
|
||||
document_im,
|
||||
document_is,
|
||||
end_cep,
|
||||
end_ibge,
|
||||
end_logradouro,
|
||||
end_numero,
|
||||
end_complemento,
|
||||
end_bairro,
|
||||
end_cidade,
|
||||
end_uf,
|
||||
info_email,
|
||||
info_email_nfe,
|
||||
info_observacao,
|
||||
info_telefone,
|
||||
info_uso_consumo_ibs_cbs,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM {$this->contatoTable}
|
||||
WHERE empresa_id = :empresa_id
|
||||
AND {$identifier} = :value
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: [
|
||||
':empresa_id' => $empresa_id,
|
||||
':value' => $value,
|
||||
]
|
||||
);
|
||||
|
||||
return $result ? $this->mapToModel($result) : null;
|
||||
}
|
||||
|
||||
public function findOneByConditions(int $empresa_id, array $conditions): ?ContatoModel {
|
||||
if (empty($conditions)) {
|
||||
throw new \InvalidArgumentException('O array de condições não pode estar vazio.');
|
||||
}
|
||||
|
||||
// Começa com as condições fixas
|
||||
$whereClauses = [
|
||||
'empresa_id = :empresa_id',
|
||||
'deleted_at IS NULL'
|
||||
];
|
||||
|
||||
$params = [
|
||||
':empresa_id' => $empresa_id
|
||||
];
|
||||
|
||||
// Adiciona as condições dinâmicas
|
||||
foreach ($conditions as $condition) {
|
||||
if (!isset($condition['field'], $condition['value'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Cada condição deve conter 'field' e 'value'."
|
||||
);
|
||||
}
|
||||
|
||||
$field = $condition['field'];
|
||||
$operator = $condition['operator'] ?? '=';
|
||||
$value = $condition['value'];
|
||||
|
||||
$whereClauses[] = "{$field} {$operator} :{$field}";
|
||||
$params[":{$field}"] = $value;
|
||||
}
|
||||
|
||||
$whereSql = implode(' AND ', $whereClauses);
|
||||
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
empresa_id,
|
||||
is_active,
|
||||
tipo,
|
||||
nome_empresarial,
|
||||
nome_fantasia,
|
||||
personalidade,
|
||||
document_cpf,
|
||||
document_cnpj,
|
||||
regime_tributario,
|
||||
contribuinte_icms,
|
||||
orgao_publico,
|
||||
document_ie,
|
||||
document_im,
|
||||
document_is,
|
||||
end_pais,
|
||||
end_cep,
|
||||
end_ibge,
|
||||
end_logradouro,
|
||||
end_numero,
|
||||
end_complemento,
|
||||
end_bairro,
|
||||
end_cidade,
|
||||
end_uf,
|
||||
info_email,
|
||||
info_email_nfe,
|
||||
info_observacao,
|
||||
info_telefone,
|
||||
info_uso_consumo_ibs_cbs,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM {$this->contatoTable}
|
||||
WHERE {$whereSql}
|
||||
LIMIT 1";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: $params
|
||||
);
|
||||
|
||||
return $result ? $this->mapToModel($result) : null;
|
||||
}
|
||||
|
||||
private function mapToModel(array $data): ContatoModel {
|
||||
return new ContatoModel(
|
||||
id: $data['id'],
|
||||
uuid: $data['uuid'],
|
||||
empresa_id: $data['empresa_id'],
|
||||
is_active: $data['is_active'],
|
||||
tipo: $data['tipo'],
|
||||
nome_empresarial: $data['nome_empresarial'],
|
||||
nome_fantasia: $data['nome_fantasia'],
|
||||
personalidade: $data['personalidade'],
|
||||
document_cpf: $data['document_cpf'],
|
||||
document_cnpj: $data['document_cnpj'],
|
||||
regime_tributario: $data['regime_tributario'],
|
||||
contribuinte_icms: $data['contribuinte_icms'],
|
||||
orgao_publico: $data['orgao_publico'],
|
||||
document_ie: $data['document_ie'],
|
||||
document_im: $data['document_im'],
|
||||
document_is: $data['document_is'],
|
||||
end_pais: $data['end_pais'],
|
||||
end_cep: $data['end_cep'],
|
||||
end_ibge: $data['end_ibge'],
|
||||
end_logradouro: $data['end_logradouro'],
|
||||
end_numero: $data['end_numero'],
|
||||
end_complemento: $data['end_complemento'],
|
||||
end_bairro: $data['end_bairro'],
|
||||
end_cidade: $data['end_cidade'],
|
||||
end_uf: $data['end_uf'],
|
||||
info_email: $data['info_email'],
|
||||
info_email_nfe: $data['info_email_nfe'],
|
||||
info_observacao: $data['info_observacao'],
|
||||
info_telefone: $data['info_telefone'],
|
||||
info_uso_consumo_ibs_cbs: $data['info_uso_consumo_ibs_cbs'],
|
||||
created_at: $data['created_at'] ? new DateTimeImmutable($data['created_at']) : null,
|
||||
updated_at: $data['updated_at'] ? new DateTimeImmutable($data['updated_at']) : null,
|
||||
deleted_at: $data['deleted_at'] ? new DateTimeImmutable($data['deleted_at']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
use KrothiumAPI\Http\Router;
|
||||
use WorkbloomERP\Module\v0\Auth\Middlewares\AuthMiddleware;
|
||||
use WorkbloomERP\Module\v0\Contato\Controllers\ContatoController;
|
||||
|
||||
Router::group(
|
||||
prefix: '/contacts',
|
||||
callback: function() {
|
||||
// Endpoint para obter as opções de criação de contato (UF, regime tributário, etc.)
|
||||
Router::get('/create-options', [ContatoController::class, 'createOptions']);
|
||||
|
||||
// Rota para cadastrar um novo contato
|
||||
Router::post('/create', [ContatoController::class, 'create']);
|
||||
},
|
||||
middlewares: [
|
||||
[AuthMiddleware::class, 'handle']
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Contato\Services;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use WorkbloomERP\Utils\SanitizeUtil;
|
||||
use WorkbloomERP\Utils\ValidateUtil;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
use WorkbloomERP\Module\v0\Auth\Utils\AuthUtil;
|
||||
use WorkbloomERP\Module\v0\Empresa\Repos\EmpresaRepo;
|
||||
use WorkbloomERP\Module\v0\Contato\Repos\ContatoRepo;
|
||||
use WorkbloomERP\Module\v0\Empresa\Models\EmpresaModel;
|
||||
use WorkbloomERP\Module\v0\Contato\Models\ContatoModel;
|
||||
use WorkbloomERP\Module\v0\Contato\DTOs\ContatoCreateDTO;
|
||||
use WorkbloomERP\Module\v0\Contato\Factories\ContatoServiceFactory;
|
||||
use WorkbloomERP\Constants\SpedCRTConst;
|
||||
use WorkbloomERP\Constants\BrasilUfsConst;
|
||||
use WorkbloomERP\Constants\SpedPaisesConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\OrgaoPublicoConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\PersonalidadeConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\ContribuinteICMSConst;
|
||||
use WorkbloomERP\Module\v0\Contato\Constants\NFSeConsumoIbsCbsConst;
|
||||
|
||||
class ContatoService {
|
||||
public function __construct (
|
||||
protected DBService $db,
|
||||
protected ContatoRepo $contatoRepo,
|
||||
protected EmpresaRepo $empresaRepo,
|
||||
) {}
|
||||
|
||||
public function createOptions(): array {
|
||||
return [
|
||||
'tipo' => PersonalidadeConst::getAll(),
|
||||
'personalidade' => PersonalidadeConst::getAll(),
|
||||
'uf' => BrasilUfsConst::getAll(),
|
||||
'regime_tributario' => SpedCRTConst::getAll(),
|
||||
'contribuinte_icms' => ContribuinteICMSConst::getAll(),
|
||||
'info_uso_consumo_ibs_cbs' => NFSeConsumoIbsCbsConst::getAll(),
|
||||
'orgao_publico' => OrgaoPublicoConst::getAll(),
|
||||
'paises_sped' => SpedPaisesConst::getAll()
|
||||
];
|
||||
}
|
||||
|
||||
public function create(ContatoCreateDTO $contatoCreateDTO): array {
|
||||
try {
|
||||
return $this->db->transaction(
|
||||
callback: function() use ($contatoCreateDTO) {
|
||||
// Pega as informações da sessão para associar o contato à empresa correta
|
||||
$empresaData = AuthUtil::readSession(key: 'empresa_data');
|
||||
|
||||
// Verifica se a empresa existe
|
||||
$empresaModel = $this->empresaRepo->findByIdentifier(identifier: 'uuid', value: $empresaData['uuid']);
|
||||
|
||||
// Cria a fábrica de contato e valida os dados de criação do contato
|
||||
$contatoFactory = ContatoServiceFactory::makeContratoFactory(empresaModel: $empresaModel, contatoCreateDTO: $contatoCreateDTO);
|
||||
$contatoModel = $contatoFactory->create(contatoCreateDTO: $contatoCreateDTO, empresaModel: $empresaModel);
|
||||
|
||||
return [
|
||||
'response_code' => 201,
|
||||
'message' => 'Contato criado com sucesso.',
|
||||
'output' => ['data' => $contatoModel->toArray()]
|
||||
];
|
||||
}
|
||||
);
|
||||
} catch(AppException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Empresa\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
class EmpresaModel {
|
||||
public function __construct(
|
||||
private ?int $id = null,
|
||||
private ?string $uuid = null,
|
||||
private ?bool $is_active = null,
|
||||
private ?string $nome_empresarial = null,
|
||||
private ?string $nome_fantasia = null,
|
||||
private ?string $tipo = null,
|
||||
private ?int $matriz_id = null,
|
||||
private ?string $document_cnpj = null,
|
||||
private ?string $document_ie = null,
|
||||
private ?string $document_im = null,
|
||||
private ?string $regime_tributario = null,
|
||||
private ?string $end_cep = null,
|
||||
private ?int $end_ibge = null,
|
||||
private ?string $end_logradouro = null,
|
||||
private ?string $end_numero = null,
|
||||
private ?string $end_complemento = null,
|
||||
private ?string $end_bairro = null,
|
||||
private ?string $end_cidade = null,
|
||||
private ?string $end_uf = null,
|
||||
private ?DateTimeImmutable $created_at = null,
|
||||
private ?DateTimeImmutable $updated_at = null,
|
||||
private ?DateTimeImmutable $deleted_at = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'uuid' => $this->getUuid(),
|
||||
'is_active' => $this->getIsActive(),
|
||||
'nome_empresarial' => $this->getNomeEmpresarial(),
|
||||
'nome_fantasia' => $this->getNomeFantasia(),
|
||||
'tipo' => $this->getTipo(),
|
||||
'matriz_id' => $this->getMatrizId(),
|
||||
'document_cnpj' => $this->getDocumentCnpj(),
|
||||
'document_ie' => $this->getDocumentIe(),
|
||||
'document_im' => $this->getDocumentIm(),
|
||||
'regime_tributario' => $this->getRegimeTributario(),
|
||||
'end_cep' => $this->getEndCep(),
|
||||
'end_ibge' => $this->getEndIbge(),
|
||||
'end_logradouro' => $this->getEndLogradouro(),
|
||||
'end_numero' => $this->getEndNumero(),
|
||||
'end_complemento' => $this->getEndComplemento(),
|
||||
'end_bairro' => $this->getEndBairro(),
|
||||
'end_cidade' => $this->getEndCidade(),
|
||||
'end_uf' => $this->getEndUf(),
|
||||
'created_at' => $this->getCreatedAt() ? $this->getCreatedAt()->format('Y-m-d H:i:s') : null,
|
||||
'updated_at' => $this->getUpdatedAt() ? $this->getUpdatedAt()->format('Y-m-d H:i:s') : null,
|
||||
'deleted_at' => $this->getDeletedAt() ? $this->getDeletedAt()->format('Y-m-d H:i:s') : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function setId(?int $id): void {
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId(): ?int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setUuid(?string $uuid): void {
|
||||
$this->uuid = $uuid;
|
||||
}
|
||||
public function getUuid(): ?string {
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function setIsActive(?bool $is_active): void {
|
||||
$this->is_active = $is_active;
|
||||
}
|
||||
public function getIsActive(): ?bool {
|
||||
return $this->is_active;
|
||||
}
|
||||
|
||||
public function setNomeEmpresarial(?string $nome_empresarial): void {
|
||||
$this->nome_empresarial = $nome_empresarial;
|
||||
}
|
||||
public function getNomeEmpresarial(): ?string {
|
||||
return $this->nome_empresarial;
|
||||
}
|
||||
|
||||
public function setNomeFantasia(?string $nome_fantasia): void {
|
||||
$this->nome_fantasia = $nome_fantasia;
|
||||
}
|
||||
public function getNomeFantasia(): ?string {
|
||||
return $this->nome_fantasia;
|
||||
}
|
||||
|
||||
public function setTipo(?string $tipo): void {
|
||||
$this->tipo = $tipo;
|
||||
}
|
||||
public function getTipo(): ?string {
|
||||
return $this->tipo;
|
||||
}
|
||||
|
||||
public function setMatrizId(?int $matriz_id): void {
|
||||
$this->matriz_id = $matriz_id;
|
||||
}
|
||||
public function getMatrizId(): ?int {
|
||||
return $this->matriz_id;
|
||||
}
|
||||
|
||||
public function setDocumentCnpj(?string $document_cnpj): void {
|
||||
$this->document_cnpj = $document_cnpj;
|
||||
}
|
||||
public function getDocumentCnpj(): ?string {
|
||||
return $this->document_cnpj;
|
||||
}
|
||||
|
||||
public function setDocumentIe(?string $document_ie): void {
|
||||
$this->document_ie = $document_ie;
|
||||
}
|
||||
public function getDocumentIe(): ?string {
|
||||
return $this->document_ie;
|
||||
}
|
||||
|
||||
public function setDocumentIm(?string $document_im): void {
|
||||
$this->document_im = $document_im;
|
||||
}
|
||||
public function getDocumentIm(): ?string {
|
||||
return $this->document_im;
|
||||
}
|
||||
|
||||
public function setRegimeTributario(?string $regime_tributario): void {
|
||||
$this->regime_tributario = $regime_tributario;
|
||||
}
|
||||
public function getRegimeTributario(): ?string {
|
||||
return $this->regime_tributario;
|
||||
}
|
||||
|
||||
public function setEndCep(?string $end_cep): void {
|
||||
$this->end_cep = $end_cep;
|
||||
}
|
||||
public function getEndCep(): ?string {
|
||||
return $this->end_cep;
|
||||
}
|
||||
|
||||
public function setEndIbge(?int $end_ibge): void {
|
||||
$this->end_ibge = $end_ibge;
|
||||
}
|
||||
public function getEndIbge(): ?int {
|
||||
return $this->end_ibge;
|
||||
}
|
||||
|
||||
public function setEndLogradouro(?string $end_logradouro): void {
|
||||
$this->end_logradouro = $end_logradouro;
|
||||
}
|
||||
public function getEndLogradouro(): ?string {
|
||||
return $this->end_logradouro;
|
||||
}
|
||||
|
||||
public function setEndNumero(?string $end_numero): void {
|
||||
$this->end_numero = $end_numero;
|
||||
}
|
||||
public function getEndNumero(): ?string {
|
||||
return $this->end_numero;
|
||||
}
|
||||
|
||||
public function setEndComplemento(?string $end_complemento): void {
|
||||
$this->end_complemento = $end_complemento;
|
||||
}
|
||||
public function getEndComplemento(): ?string {
|
||||
return $this->end_complemento;
|
||||
}
|
||||
|
||||
public function setEndBairro(?string $end_bairro): void {
|
||||
$this->end_bairro = $end_bairro;
|
||||
}
|
||||
public function getEndBairro(): ?string {
|
||||
return $this->end_bairro;
|
||||
}
|
||||
|
||||
public function setEndCidade(?string $end_cidade): void {
|
||||
$this->end_cidade = $end_cidade;
|
||||
}
|
||||
public function getEndCidade(): ?string {
|
||||
return $this->end_cidade;
|
||||
}
|
||||
|
||||
public function setEndUf(?string $end_uf): void {
|
||||
$this->end_uf = $end_uf;
|
||||
}
|
||||
public function getEndUf(): ?string {
|
||||
return $this->end_uf;
|
||||
}
|
||||
|
||||
public function setCreatedAt(?DateTimeImmutable $created_at): void {
|
||||
$this->created_at = $created_at;
|
||||
}
|
||||
public function getCreatedAt(): ?DateTimeImmutable {
|
||||
return $this->created_at;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeImmutable $updated_at): void {
|
||||
$this->updated_at = $updated_at;
|
||||
}
|
||||
public function getUpdatedAt(): ?DateTimeImmutable {
|
||||
return $this->updated_at;
|
||||
}
|
||||
|
||||
public function setDeletedAt(?DateTimeImmutable $deleted_at): void {
|
||||
$this->deleted_at = $deleted_at;
|
||||
}
|
||||
public function getDeletedAt(): ?DateTimeImmutable {
|
||||
return $this->deleted_at;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Empresa\Repos;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Empresa\Models\EmpresaModel;
|
||||
|
||||
class EmpresaRepo {
|
||||
protected string $empresaTable = 'empresa';
|
||||
|
||||
public function __construct(
|
||||
protected DBService $db
|
||||
) {}
|
||||
|
||||
public function insert(EmpresaModel $empresaModel): ?EmpresaModel {
|
||||
$query =
|
||||
"INSERT INTO {$this->empresaTable} (
|
||||
uuid,
|
||||
is_active,
|
||||
nome_empresarial,
|
||||
nome_fantasia,
|
||||
tipo,
|
||||
matriz_id,
|
||||
document_cnpj,
|
||||
document_ie,
|
||||
document_im,
|
||||
regime_tributario,
|
||||
end_cep,
|
||||
end_ibge,
|
||||
end_logradouro,
|
||||
end_numero,
|
||||
end_complemento,
|
||||
end_bairro,
|
||||
end_cidade,
|
||||
end_uf,
|
||||
created_at
|
||||
) VALUES (
|
||||
:uuid,
|
||||
:is_active,
|
||||
:nome_empresarial,
|
||||
:nome_fantasia,
|
||||
:tipo,
|
||||
:matriz_id,
|
||||
:document_cnpj,
|
||||
:document_ie,
|
||||
:document_im,
|
||||
:regime_tributario,
|
||||
:end_cep,
|
||||
:end_ibge,
|
||||
:end_logradouro,
|
||||
:end_numero,
|
||||
:end_complemento,
|
||||
:end_bairro,
|
||||
:end_cidade,
|
||||
:end_uf,
|
||||
:created_at
|
||||
)";
|
||||
|
||||
$empresaModel->setCreatedAt(new DateTimeImmutable());
|
||||
|
||||
$this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':uuid' => $empresaModel->getUuid(),
|
||||
':is_active' => $empresaModel->getIsActive(),
|
||||
':nome_empresarial' => $empresaModel->getNomeEmpresarial(),
|
||||
':nome_fantasia' => $empresaModel->getNomeFantasia(),
|
||||
':tipo' => $empresaModel->getTipo(),
|
||||
':matriz_id' => $empresaModel->getMatrizId(),
|
||||
':document_cnpj' => $empresaModel->getDocumentCnpj(),
|
||||
':document_ie' => $empresaModel->getDocumentIe(),
|
||||
':document_im' => $empresaModel->getDocumentIm(),
|
||||
':regime_tributario' => $empresaModel->getRegimeTributario(),
|
||||
':end_cep' => $empresaModel->getEndCep(),
|
||||
':end_ibge' => $empresaModel->getEndIbge(),
|
||||
':end_logradouro' => $empresaModel->getEndLogradouro(),
|
||||
':end_numero' => $empresaModel->getEndNumero(),
|
||||
':end_complemento' => $empresaModel->getEndComplemento(),
|
||||
':end_bairro' => $empresaModel->getEndBairro(),
|
||||
':end_cidade' => $empresaModel->getEndCidade(),
|
||||
':end_uf' => $empresaModel->getEndUf(),
|
||||
':created_at' => $empresaModel->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
|
||||
$empresaModel->setId($this->db->lastInsertId());
|
||||
return $empresaModel;
|
||||
}
|
||||
|
||||
public function update(EmpresaModel $empresaModel): bool {
|
||||
$query =
|
||||
"UPDATE {$this->empresaTable} SET
|
||||
is_active = :is_active,
|
||||
nome_empresarial = :nome_empresarial,
|
||||
nome_fantasia = :nome_fantasia,
|
||||
tipo = :tipo,
|
||||
matriz_id = :matriz_id,
|
||||
document_cnpj = :document_cnpj,
|
||||
document_ie = :document_ie,
|
||||
document_im = :document_im,
|
||||
regime_tributario = :regime_tributario,
|
||||
end_cep = :end_cep,
|
||||
end_ibge = :end_ibge,
|
||||
end_logradouro = :end_logradouro,
|
||||
end_numero = :end_numero,
|
||||
end_complemento = :end_complemento,
|
||||
end_bairro = :end_bairro,
|
||||
end_cidade = :end_cidade,
|
||||
end_uf = :end_uf,
|
||||
updated_at = :updated_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
$empresaModel->setUpdatedAt(new DateTimeImmutable());
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $empresaModel->getId(),
|
||||
':uuid' => $empresaModel->getUuid(),
|
||||
':is_active' => $empresaModel->getIsActive(),
|
||||
':nome_empresarial' => $empresaModel->getNomeEmpresarial(),
|
||||
':nome_fantasia' => $empresaModel->getNomeFantasia(),
|
||||
':tipo' => $empresaModel->getTipo(),
|
||||
':matriz_id' => $empresaModel->getMatrizId(),
|
||||
':document_cnpj' => $empresaModel->getDocumentCnpj(),
|
||||
':document_ie' => $empresaModel->getDocumentIe(),
|
||||
':document_im' => $empresaModel->getDocumentIm(),
|
||||
':regime_tributario' => $empresaModel->getRegimeTributario(),
|
||||
':end_cep' => $empresaModel->getEndCep(),
|
||||
':end_ibge' => $empresaModel->getEndIbge(),
|
||||
':end_logradouro' => $empresaModel->getEndLogradouro(),
|
||||
':end_numero' => $empresaModel->getEndNumero(),
|
||||
':end_complemento' => $empresaModel->getEndComplemento(),
|
||||
':end_bairro' => $empresaModel->getEndBairro(),
|
||||
':end_cidade' => $empresaModel->getEndCidade(),
|
||||
':end_uf' => $empresaModel->getEndUf(),
|
||||
':updated_at' => $empresaModel->getUpdatedAt()->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(EmpresaModel $empresaModel): bool {
|
||||
$query =
|
||||
"UPDATE {$this->empresaTable} SET
|
||||
deleted_at = :deleted_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
$empresaModel->setDeletedAt(new DateTimeImmutable());
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $empresaModel->getId(),
|
||||
':uuid' => $empresaModel->getUuid(),
|
||||
':deleted_at' => $empresaModel->getDeletedAt()->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function findByIdentifier(string $identifier, mixed $value): ?EmpresaModel {
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
is_active,
|
||||
nome_empresarial,
|
||||
nome_fantasia,
|
||||
tipo,
|
||||
matriz_id,
|
||||
document_cnpj,
|
||||
document_ie,
|
||||
document_im,
|
||||
regime_tributario,
|
||||
end_cep,
|
||||
end_ibge,
|
||||
end_logradouro,
|
||||
end_numero,
|
||||
end_complemento,
|
||||
end_bairro,
|
||||
end_cidade,
|
||||
end_uf,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM {$this->empresaTable}
|
||||
WHERE $identifier = :value
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: [
|
||||
':value' => $value
|
||||
]
|
||||
);
|
||||
|
||||
return $result ? $this->mapToModel($result) : null;
|
||||
}
|
||||
|
||||
public function findAllByMatrizId(int $matriz_id): array {
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
is_active,
|
||||
nome_empresarial,
|
||||
nome_fantasia,
|
||||
tipo,
|
||||
matriz_id,
|
||||
document_cnpj,
|
||||
document_ie,
|
||||
document_im,
|
||||
regime_tributario,
|
||||
end_cep,
|
||||
end_ibge,
|
||||
end_logradouro,
|
||||
end_numero,
|
||||
end_complemento,
|
||||
end_bairro,
|
||||
end_cidade,
|
||||
end_uf,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM {$this->empresaTable}
|
||||
WHERE (
|
||||
id = :matriz_id OR
|
||||
matriz_id = :matriz_id
|
||||
)
|
||||
AND deleted_at IS NULL";
|
||||
|
||||
$results = $this->db->fetchAll(
|
||||
sql: $query,
|
||||
params: [
|
||||
':matriz_id' => $matriz_id
|
||||
]
|
||||
);
|
||||
|
||||
return array_map(fn($data) => $this->mapToModel($data), $results);
|
||||
}
|
||||
|
||||
private function mapToModel(array $data): EmpresaModel {
|
||||
return new EmpresaModel(
|
||||
id: $data['id'],
|
||||
uuid: $data['uuid'],
|
||||
is_active: $data['is_active'],
|
||||
nome_empresarial: $data['nome_empresarial'],
|
||||
nome_fantasia: $data['nome_fantasia'],
|
||||
tipo: $data['tipo'],
|
||||
matriz_id: $data['matriz_id'],
|
||||
document_cnpj: $data['document_cnpj'],
|
||||
document_ie: $data['document_ie'],
|
||||
document_im: $data['document_im'],
|
||||
regime_tributario: $data['regime_tributario'],
|
||||
end_cep: $data['end_cep'],
|
||||
end_ibge: $data['end_ibge'],
|
||||
end_logradouro: $data['end_logradouro'],
|
||||
end_numero: $data['end_numero'],
|
||||
end_complemento: $data['end_complemento'],
|
||||
end_bairro: $data['end_bairro'],
|
||||
end_cidade: $data['end_cidade'],
|
||||
end_uf: $data['end_uf'],
|
||||
created_at: $data['created_at'] ? new DateTimeImmutable($data['created_at']) : null,
|
||||
updated_at: $data['updated_at'] ? new DateTimeImmutable($data['updated_at']) : null,
|
||||
deleted_at: $data['deleted_at'] ? new DateTimeImmutable($data['deleted_at']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Usuario\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
class UsuarioEmpresaModel {
|
||||
public function __construct(
|
||||
private ?int $usuario_id = null,
|
||||
private ?int $empresa_id = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'usuario_id' => $this->getUsuarioId(),
|
||||
'empresa_id' => $this->getEmpresaId(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getUsuarioId(): ?int {
|
||||
return $this->usuario_id;
|
||||
}
|
||||
public function setUsuarioId(?int $usuario_id): void {
|
||||
$this->usuario_id = $usuario_id;
|
||||
}
|
||||
|
||||
public function getEmpresaId(): ?int {
|
||||
return $this->empresa_id;
|
||||
}
|
||||
public function setEmpresaId(?int $empresa_id): void {
|
||||
$this->empresa_id = $empresa_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Usuario\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
class UsuarioModel {
|
||||
public function __construct(
|
||||
private ?int $id = null,
|
||||
private ?string $uuid = null,
|
||||
private ?bool $is_active = null,
|
||||
private ?bool $is_root = null,
|
||||
private ?string $nome_completo = null,
|
||||
private ?string $nome_usuario = null,
|
||||
private ?string $email = null,
|
||||
private ?string $senha_hash = null,
|
||||
private ?DateTimeImmutable $created_at = null,
|
||||
private ?DateTimeImmutable $updated_at = null,
|
||||
private ?DateTimeImmutable $deleted_at = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'uuid' => $this->getUuid(),
|
||||
'is_active' => $this->getIsActive(),
|
||||
'is_root' => $this->getIsRoot(),
|
||||
'nome_completo' => $this->getNomeCompleto(),
|
||||
'nome_usuario' => $this->getNomeUsuario(),
|
||||
'email' => $this->getEmail(),
|
||||
'senha_hash' => $this->getSenhaHash(),
|
||||
'created_at' => $this->getCreatedAt() ? $this->getCreatedAt()->format('Y-m-d H:i:s') : null,
|
||||
'updated_at' => $this->getUpdatedAt() ? $this->getUpdatedAt()->format('Y-m-d H:i:s') : null,
|
||||
'deleted_at' => $this->getDeletedAt() ? $this->getDeletedAt()->format('Y-m-d H:i:s') : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function setId(?int $id): void {
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId(): ?int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setUuid(?string $uuid): void {
|
||||
$this->uuid = $uuid;
|
||||
}
|
||||
public function getUuid(): ?string {
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function setIsActive(?bool $is_active): void {
|
||||
$this->is_active = $is_active;
|
||||
}
|
||||
public function getIsActive(): ?bool {
|
||||
return $this->is_active;
|
||||
}
|
||||
|
||||
public function setIsRoot(?bool $is_root): void {
|
||||
$this->is_root = $is_root;
|
||||
}
|
||||
public function getIsRoot(): ?bool {
|
||||
return $this->is_root;
|
||||
}
|
||||
|
||||
public function setNomeCompleto(?string $nome_completo): void {
|
||||
$this->nome_completo = $nome_completo;
|
||||
}
|
||||
public function getNomeCompleto(): ?string {
|
||||
return $this->nome_completo;
|
||||
}
|
||||
|
||||
public function setNomeUsuario(?string $nome_usuario): void {
|
||||
$this->nome_usuario = $nome_usuario;
|
||||
}
|
||||
public function getNomeUsuario(): ?string {
|
||||
return $this->nome_usuario;
|
||||
}
|
||||
|
||||
public function setEmail(?string $email): void {
|
||||
$this->email = $email;
|
||||
}
|
||||
public function getEmail(): ?string {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setSenhaHash(?string $senha_hash): void {
|
||||
$this->senha_hash = $senha_hash;
|
||||
}
|
||||
public function getSenhaHash(): ?string {
|
||||
return $this->senha_hash;
|
||||
}
|
||||
|
||||
public function setCreatedAt(?DateTimeImmutable $created_at): void {
|
||||
$this->created_at = $created_at;
|
||||
}
|
||||
public function getCreatedAt(): ?DateTimeImmutable {
|
||||
return $this->created_at;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeImmutable $updated_at): void {
|
||||
$this->updated_at = $updated_at;
|
||||
}
|
||||
public function getUpdatedAt(): ?DateTimeImmutable {
|
||||
return $this->updated_at;
|
||||
}
|
||||
|
||||
public function setDeletedAt(?DateTimeImmutable $deleted_at): void {
|
||||
$this->deleted_at = $deleted_at;
|
||||
}
|
||||
public function getDeletedAt(): ?DateTimeImmutable {
|
||||
return $this->deleted_at;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Usuario\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
class UsuarioSessionModel {
|
||||
public function __construct(
|
||||
private ?int $id = null,
|
||||
private ?string $uuid = null,
|
||||
private ?int $usuario_id = null,
|
||||
private ?string $user_agent = null,
|
||||
private ?string $ip_address = null,
|
||||
private ?string $token_hash = null,
|
||||
private ?DateTimeImmutable $created_at = null,
|
||||
private ?DateTimeImmutable $revoked_at = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'uuid' => $this->getUuid(),
|
||||
'usuario_id' => $this->getUsuarioId(),
|
||||
'user_agent' => $this->getUserAgent(),
|
||||
'ip_address' => $this->getIpAddress(),
|
||||
'token_hash' => $this->getTokenHash(),
|
||||
'created_at' => $this->getCreatedAt() ? $this->getCreatedAt()->format('Y-m-d H:i:s') : null,
|
||||
'revoked_at' => $this->getRevokedAt() ? $this->getRevokedAt()->format('Y-m-d H:i:s') : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function setId(?int $id): void {
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId(): ?int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setUuid(?string $uuid): void {
|
||||
$this->uuid = $uuid;
|
||||
}
|
||||
public function getUuid(): ?string {
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function setUsuarioId(?int $usuario_id): void {
|
||||
$this->usuario_id = $usuario_id;
|
||||
}
|
||||
public function getUsuarioId(): ?int {
|
||||
return $this->usuario_id;
|
||||
}
|
||||
|
||||
public function setUserAgent(?string $user_agent): void {
|
||||
$this->user_agent = $user_agent;
|
||||
}
|
||||
public function getUserAgent(): ?string {
|
||||
return $this->user_agent;
|
||||
}
|
||||
|
||||
public function setIpAddress(?string $ip_address): void {
|
||||
$this->ip_address = $ip_address;
|
||||
}
|
||||
public function getIpAddress(): ?string {
|
||||
return $this->ip_address;
|
||||
}
|
||||
|
||||
public function setTokenHash(?string $token_hash): void {
|
||||
$this->token_hash = $token_hash;
|
||||
}
|
||||
public function getTokenHash(): ?string {
|
||||
return $this->token_hash;
|
||||
}
|
||||
|
||||
public function setCreatedAt(?DateTimeImmutable $created_at): void {
|
||||
$this->created_at = $created_at;
|
||||
}
|
||||
public function getCreatedAt(): ?DateTimeImmutable {
|
||||
return $this->created_at;
|
||||
}
|
||||
|
||||
public function setRevokedAt(?DateTimeImmutable $revoked_at): void {
|
||||
$this->revoked_at = $revoked_at;
|
||||
}
|
||||
public function getRevokedAt(): ?DateTimeImmutable {
|
||||
return $this->revoked_at;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Usuario\Repos;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioEmpresaModel;
|
||||
|
||||
class UsuarioEmpresaRepo {
|
||||
protected string $usuarioEmpresaTable = 'shared.usuario_empresa';
|
||||
|
||||
public function __construct(
|
||||
private DBService $db
|
||||
) {}
|
||||
|
||||
public function insert(UsuarioEmpresaModel $usuarioEmpresaModel): bool {
|
||||
$query =
|
||||
"INSERT INTO {$this->usuarioEmpresaTable} (
|
||||
usuario_id,
|
||||
empresa_id
|
||||
) VALUES (
|
||||
:usuario_id,
|
||||
:empresa_id
|
||||
)";
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
'usuario_id' => $usuarioEmpresaModel->getUsuarioId(),
|
||||
'empresa_id' => $usuarioEmpresaModel->getEmpresaId()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(UsuarioEmpresaModel $usuarioEmpresaModel): bool {
|
||||
$query =
|
||||
"DELETE FROM {$this->usuarioEmpresaTable} WHERE usuario_id = :usuario_id AND empresa_id = :empresa_id";
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
'usuario_id' => $usuarioEmpresaModel->getUsuarioId(),
|
||||
'empresa_id' => $usuarioEmpresaModel->getEmpresaId()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function findAllByUsuarioId(int $usuario_id): array {
|
||||
$query =
|
||||
"SELECT
|
||||
usuario_id,
|
||||
empresa_id
|
||||
FROM {$this->usuarioEmpresaTable}
|
||||
WHERE usuario_id = :usuario_id";
|
||||
|
||||
return $this->db->fetchAll(
|
||||
sql: $query,
|
||||
params: [
|
||||
'usuario_id' => $usuario_id
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function checkAssociationByUsuarioIdAndEmpresaId(int $usuario_id, int $empresa_id): bool {
|
||||
$query =
|
||||
"SELECT 1 FROM {$this->usuarioEmpresaTable} WHERE usuario_id = :usuario_id AND empresa_id = :empresa_id";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: [
|
||||
'usuario_id' => $usuario_id,
|
||||
'empresa_id' => $empresa_id
|
||||
]
|
||||
);
|
||||
|
||||
return !empty($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Usuario\Repos;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioModel;
|
||||
|
||||
class UsuarioRepo {
|
||||
protected string $usuarioTable = 'usuario';
|
||||
|
||||
public function __construct(
|
||||
private DBService $db
|
||||
) {}
|
||||
|
||||
public function insert(UsuarioModel $usuarioModel): ?UsuarioModel {
|
||||
$query =
|
||||
"INSERT INTO {$this->usuarioTable} (
|
||||
uuid,
|
||||
is_active,
|
||||
is_root,
|
||||
nome_completo,
|
||||
nome_usuario,
|
||||
email,
|
||||
senha_hash,
|
||||
created_at
|
||||
) VALUES (
|
||||
:uuid,
|
||||
:is_active,
|
||||
:is_root,
|
||||
:nome_completo,
|
||||
:nome_usuario,
|
||||
:email,
|
||||
:senha_hash,
|
||||
:created_at
|
||||
)";
|
||||
|
||||
$usuarioModel->setCreatedAt(created_at: new DateTimeImmutable());
|
||||
|
||||
$this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
'uuid' => $usuarioModel->getUuid(),
|
||||
'is_active' => $usuarioModel->getIsActive(),
|
||||
'is_root' => $usuarioModel->getIsRoot(),
|
||||
'nome_completo' => $usuarioModel->getNomeCompleto(),
|
||||
'nome_usuario' => $usuarioModel->getNomeUsuario(),
|
||||
'email' => $usuarioModel->getEmail(),
|
||||
'senha_hash' => $usuarioModel->getSenhaHash(),
|
||||
'created_at' => $usuarioModel->getCreatedAt()->format(format: 'Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
|
||||
$usuarioModel->setId(id: $this->db->lastInsertId());
|
||||
return $usuarioModel;
|
||||
}
|
||||
|
||||
public function update(UsuarioModel $usuarioModel): ?UsuarioModel {
|
||||
$query =
|
||||
"UPDATE {$this->usuarioTable} SET
|
||||
is_active = :is_active,
|
||||
is_root = :is_root,
|
||||
nome_completo = :nome_completo,
|
||||
nome_usuario = :nome_usuario,
|
||||
email = :email,
|
||||
senha_hash = :senha_hash,
|
||||
updated_at = :updated_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
$usuarioModel->setUpdatedAt(updated_at: new DateTimeImmutable());
|
||||
$this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $usuarioModel->getId(),
|
||||
':uuid' => $usuarioModel->getUuid(),
|
||||
':is_active' => $usuarioModel->getIsActive(),
|
||||
':is_root' => $usuarioModel->getIsRoot(),
|
||||
':nome_completo' => $usuarioModel->getNomeCompleto(),
|
||||
':nome_usuario' => $usuarioModel->getNomeUsuario(),
|
||||
':email' => $usuarioModel->getEmail(),
|
||||
':senha_hash' => $usuarioModel->getSenhaHash(),
|
||||
':updated_at' => $usuarioModel->getUpdatedAt()->format(format: 'Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
|
||||
return $usuarioModel;
|
||||
}
|
||||
|
||||
public function delete(UsuarioModel $usuarioModel): ?UsuarioModel {
|
||||
$query =
|
||||
"UPDATE {$this->usuarioTable} SET
|
||||
is_active = 0,
|
||||
deleted_at = :deleted_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
$usuarioModel->setDeletedAt(deleted_at: new DateTimeImmutable());
|
||||
|
||||
$this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $usuarioModel->getId(),
|
||||
':uuid' => $usuarioModel->getUuid(),
|
||||
':deleted_at' => $usuarioModel->getDeletedAt()->format(format: 'Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
|
||||
return $usuarioModel;
|
||||
}
|
||||
|
||||
public function findByIdentifier(string $identifier, mixed $value): ?UsuarioModel {
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
is_active,
|
||||
is_root,
|
||||
nome_completo,
|
||||
nome_usuario,
|
||||
email,
|
||||
senha_hash,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM {$this->usuarioTable}
|
||||
WHERE {$identifier} = :value
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: [
|
||||
':value' => $value
|
||||
]
|
||||
);
|
||||
|
||||
return $result ? $this->mapToModel($result) : null;
|
||||
}
|
||||
|
||||
public function findByLogin(string $login): ?UsuarioModel {
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
is_active,
|
||||
is_root,
|
||||
nome_completo,
|
||||
nome_usuario,
|
||||
email,
|
||||
senha_hash,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM {$this->usuarioTable}
|
||||
WHERE (nome_usuario = :login OR email = :login)
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: [
|
||||
':login' => $login
|
||||
]
|
||||
);
|
||||
|
||||
return $result ? $this->mapToModel($result) : null;
|
||||
}
|
||||
|
||||
private function mapToModel(array $data): UsuarioModel {
|
||||
return new UsuarioModel(
|
||||
id: $data['id'],
|
||||
uuid: $data['uuid'],
|
||||
is_active: $data['is_active'],
|
||||
is_root: $data['is_root'],
|
||||
nome_completo: $data['nome_completo'],
|
||||
nome_usuario: $data['nome_usuario'],
|
||||
email: $data['email'],
|
||||
senha_hash: $data['senha_hash'],
|
||||
created_at: $data['created_at'] ? new DateTimeImmutable($data['created_at']) : null,
|
||||
updated_at: $data['updated_at'] ? new DateTimeImmutable($data['updated_at']) : null,
|
||||
deleted_at: $data['deleted_at'] ? new DateTimeImmutable($data['deleted_at']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Module\v0\Usuario\Repos;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use WorkbloomERP\Services\DBService;
|
||||
use WorkbloomERP\Module\v0\Usuario\Models\UsuarioSessionModel;
|
||||
|
||||
class UsuarioSessionRepo {
|
||||
protected string $usuarioSessionTable = 'usuario_session';
|
||||
|
||||
public function __construct(
|
||||
private DBService $db
|
||||
) {}
|
||||
|
||||
public function insert(UsuarioSessionModel $usuarioSessionModel): ?UsuarioSessionModel {
|
||||
$query =
|
||||
"INSERT INTO {$this->usuarioSessionTable} (
|
||||
uuid,
|
||||
usuario_id,
|
||||
user_agent,
|
||||
ip_address,
|
||||
token_hash,
|
||||
created_at
|
||||
) VALUES (
|
||||
:uuid,
|
||||
:usuario_id,
|
||||
:user_agent,
|
||||
:ip_address,
|
||||
:token_hash,
|
||||
:created_at
|
||||
)";
|
||||
|
||||
$usuarioSessionModel->setCreatedAt(new DateTimeImmutable());
|
||||
|
||||
$this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':uuid' => $usuarioSessionModel->getUuid(),
|
||||
':usuario_id' => $usuarioSessionModel->getUsuarioId(),
|
||||
':user_agent' => $usuarioSessionModel->getUserAgent(),
|
||||
':ip_address' => $usuarioSessionModel->getIpAddress(),
|
||||
':token_hash' => $usuarioSessionModel->getTokenHash(),
|
||||
':created_at' => $usuarioSessionModel->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
|
||||
$usuarioSessionModel->setId($this->db->lastInsertId());
|
||||
return $usuarioSessionModel;
|
||||
}
|
||||
|
||||
public function update(UsuarioSessionModel $usuarioSessionModel): bool {
|
||||
$query =
|
||||
"UPDATE {$this->usuarioSessionTable} SET
|
||||
usuario_id = :usuario_id,
|
||||
user_agent = :user_agent,
|
||||
ip_address = :ip_address,
|
||||
token_hash = :token_hash,
|
||||
revoked_at = :revoked_at
|
||||
WHERE id = :id OR uuid = :uuid";
|
||||
|
||||
return $this->db->execute(
|
||||
sql: $query,
|
||||
params: [
|
||||
':id' => $usuarioSessionModel->getId(),
|
||||
':uuid' => $usuarioSessionModel->getUuid(),
|
||||
':usuario_id' => $usuarioSessionModel->getUsuarioId(),
|
||||
':user_agent' => $usuarioSessionModel->getUserAgent(),
|
||||
':ip_address' => $usuarioSessionModel->getIpAddress(),
|
||||
':token_hash' => $usuarioSessionModel->getTokenHash(),
|
||||
':revoked_at' => $usuarioSessionModel->getRevokedAt() ? $usuarioSessionModel->getRevokedAt()->format('Y-m-d H:i:s') : null
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function findByIdentifier(string $identifier, mixed $value): ?UsuarioSessionModel {
|
||||
$query =
|
||||
"SELECT
|
||||
id,
|
||||
uuid,
|
||||
usuario_id,
|
||||
user_agent,
|
||||
ip_address,
|
||||
token_hash,
|
||||
created_at,
|
||||
revoked_at
|
||||
FROM {$this->usuarioSessionTable}
|
||||
WHERE $identifier = :value
|
||||
LIMIT 1";
|
||||
|
||||
$result = $this->db->fetchOne(
|
||||
sql: $query,
|
||||
params: [':value' => $value]
|
||||
);
|
||||
|
||||
return $result ? $this->mapToModel($result) : null;
|
||||
}
|
||||
|
||||
private function mapToModel(array $data) {
|
||||
return new UsuarioSessionModel(
|
||||
id: $data['id'],
|
||||
uuid: $data['uuid'],
|
||||
usuario_id: $data['usuario_id'],
|
||||
user_agent: $data['user_agent'],
|
||||
ip_address: $data['ip_address'],
|
||||
token_hash: $data['token_hash'],
|
||||
created_at: $data['created_at'] ? new DateTimeImmutable($data['created_at']) : null,
|
||||
revoked_at: $data['revoked_at'] ? new DateTimeImmutable($data['revoked_at']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Constants;
|
||||
|
||||
class BrasilUfsConst {
|
||||
public const UFS = [
|
||||
'AC' => 'Acre',
|
||||
'AL' => 'Alagoas',
|
||||
'AP' => 'Amapá',
|
||||
'AM' => 'Amazonas',
|
||||
'BA' => 'Bahia',
|
||||
'CE' => 'Ceará',
|
||||
'DF' => 'Distrito Federal',
|
||||
'ES' => 'Espírito Santo',
|
||||
'EX' => 'Estrangeiro',
|
||||
'GO' => 'Goiás',
|
||||
'MA' => 'Maranhão',
|
||||
'MT' => 'Mato Grosso',
|
||||
'MS' => 'Mato Grosso do Sul',
|
||||
'MG' => 'Minas Gerais',
|
||||
'PA' => 'Pará',
|
||||
'PB' => 'Paraíba',
|
||||
'PR' => 'Paraná',
|
||||
'PE' => 'Pernambuco',
|
||||
'PI' => 'Piauí',
|
||||
'RJ' => 'Rio de Janeiro',
|
||||
'RN' => 'Rio Grande do Norte',
|
||||
'RS' => 'Rio Grande do Sul',
|
||||
'RO' => 'Rondônia',
|
||||
'RR' => 'Roraima',
|
||||
'SC' => 'Santa Catarina',
|
||||
'SP' => 'São Paulo',
|
||||
'SE' => 'Sergipe',
|
||||
'TO' => 'Tocantins'
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::UFS;
|
||||
}
|
||||
|
||||
public static function exists(string $uf): bool {
|
||||
return isset(self::UFS[strtoupper($uf)]);
|
||||
}
|
||||
|
||||
public static function getName(string $uf): ?string {
|
||||
return self::UFS[strtoupper($uf)] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Constants;
|
||||
|
||||
class SpedCRTConst {
|
||||
public const CRT = [
|
||||
"1" => "Simples Nacional",
|
||||
"2" => "Simples Nacional - Excesso de Sublimite de Receita Bruta",
|
||||
"3" => "Regime Normal",
|
||||
"4" => "MEI - Microempreendedor Individual"
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::CRT;
|
||||
}
|
||||
|
||||
public static function getDescription(string|int|null $code): string {
|
||||
return self::CRT[$code] ?? 'Código de regime tributário desconhecido.';
|
||||
}
|
||||
|
||||
public static function exists(string|int|null $code): bool {
|
||||
return isset(self::CRT[$code]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Constants;
|
||||
|
||||
class SpedPaisesConst {
|
||||
public const PAISES = [
|
||||
"0132" => "AFEGANISTAO",
|
||||
"7560" => "AFRICA DO SUL",
|
||||
"0175" => "ALBANIA",
|
||||
"0230" => "ALEMANHA",
|
||||
"0370" => "ANDORRA",
|
||||
"0400" => "ANGOLA",
|
||||
"0418" => "ANGUILLA",
|
||||
"0434" => "ANTIGUA E BARBUDA",
|
||||
"0531" => "ARABIA SAUDITA",
|
||||
"0590" => "ARGELIA",
|
||||
"0639" => "ARGENTINA",
|
||||
"0647" => "ARMENIA",
|
||||
"0655" => "ARUBA",
|
||||
"0698" => "AUSTRALIA",
|
||||
"0728" => "AUSTRIA",
|
||||
"0736" => "AZERBAIDJAO",
|
||||
"0779" => "BAHAMAS, ILHAS",
|
||||
"0817" => "BANGLADESH",
|
||||
"0833" => "BARBADOS",
|
||||
"0809" => "BAREIN",
|
||||
"0850" => "BELARUS",
|
||||
"0876" => "BELGICA",
|
||||
"0884" => "BELIZE",
|
||||
"2291" => "BENIN",
|
||||
"0906" => "BERMUDAS",
|
||||
"0973" => "BOLIVIA",
|
||||
"0990" => "BONAIRE, SAINT EUSTATIUS E SABA",
|
||||
"0981" => "BOSNIA-HERZEGOVINA",
|
||||
"1015" => "BOTSUANA",
|
||||
"1098" => "BOUVET, ILHA",
|
||||
"1058" => "BRASIL",
|
||||
"1082" => "BRUNEI",
|
||||
"1112" => "BULGARIA",
|
||||
"0310" => "BURKINA FASO",
|
||||
"1155" => "BURUNDI",
|
||||
"1198" => "BUTAO",
|
||||
"1279" => "CABO VERDE",
|
||||
"1457" => "CAMAROES",
|
||||
"1414" => "CAMBOJA",
|
||||
"1490" => "CANADA",
|
||||
"1546" => "CATAR",
|
||||
"1538" => "CAZAQUISTAO",
|
||||
"7889" => "CHADE",
|
||||
"1589" => "CHILE",
|
||||
"1600" => "CHINA",
|
||||
"1635" => "CHIPRE",
|
||||
"5118" => "CHRISTMAS, ILHA (NAVIDAD)",
|
||||
"7412" => "CINGAPURA",
|
||||
"1651" => "COCOS (KEELINGS)",
|
||||
"1694" => "COLOMBIA",
|
||||
"1775" => "CONGO",
|
||||
"1872" => "COREIA DO NORTE",
|
||||
"1937" => "COSTA DO MARFIM",
|
||||
"1376" => "CAYMAN",
|
||||
"1732" => "COMORES",
|
||||
"1830" => "COOK",
|
||||
"1902" => "COREIA DO SUL",
|
||||
"1961" => "COSTA RICA",
|
||||
"1953" => "CROACIA",
|
||||
"1996" => "CUBA",
|
||||
"2003" => "CURACAO",
|
||||
"2321" => "DINAMARCA",
|
||||
"7838" => "DJIBUTI",
|
||||
"2356" => "DOMINICA",
|
||||
"2402" => "EGITO",
|
||||
"6874" => "EL SALVADOR",
|
||||
"2445" => "EMIRADOS ARABES UNIDOS",
|
||||
"2399" => "EQUADOR",
|
||||
"2437" => "ERITREIA",
|
||||
"2470" => "ESLOVAQUIA",
|
||||
"2461" => "ESLOVENIA",
|
||||
"2453" => "ESPANHA",
|
||||
"2496" => "ESTADOS UNIDOS",
|
||||
"2518" => "ESTONIA",
|
||||
"7544" => "ESWATINI (ANTIGA SUAZILANDIA)",
|
||||
"2534" => "ETIOPIA",
|
||||
"2550" => "FALKLAND (MALVINAS)",
|
||||
"2593" => "FAROE",
|
||||
"8702" => "FIJI",
|
||||
"2674" => "FILIPINAS",
|
||||
"2712" => "FINLANDIA",
|
||||
"2755" => "FRANCA",
|
||||
"2810" => "GABAO",
|
||||
"2852" => "GAMBIA",
|
||||
"2895" => "GANA",
|
||||
"2917" => "GEORGIA",
|
||||
"2933" => "GIBRALTAR",
|
||||
"2976" => "GRANADA",
|
||||
"3018" => "GRECIA",
|
||||
"3050" => "GROENLANDIA",
|
||||
"3093" => "GUADALUPE",
|
||||
"3131" => "GUAM",
|
||||
"3174" => "GUATEMALA",
|
||||
"1504" => "GUERNSEY, ILHA DO CANAL (INCLUI ALDERNEY E SARK)",
|
||||
"3379" => "GUIANA",
|
||||
"3255" => "GUIANA FRANCESA",
|
||||
"3298" => "GUINE",
|
||||
"3344" => "GUINE-BISSAU",
|
||||
"3310" => "GUINE-EQUATORIAL",
|
||||
"3417" => "HAITI",
|
||||
"5738" => "HOLANDA (PAISES BAIXOS)",
|
||||
"3450" => "HONDURAS",
|
||||
"3514" => "HONG KONG",
|
||||
"3557" => "HUNGRIA",
|
||||
"3573" => "IEMEN",
|
||||
"3611" => "INDIA",
|
||||
"3654" => "INDONESIA",
|
||||
"3727" => "IRA",
|
||||
"3697" => "IRAQUE",
|
||||
"3751" => "IRLANDA",
|
||||
"3794" => "ISLANDIA",
|
||||
"3832" => "ISRAEL",
|
||||
"3867" => "ITALIA",
|
||||
"3913" => "JAMAICA",
|
||||
"3999" => "JAPAO",
|
||||
"1508" => "JERSEY, ILHA DO CANAL",
|
||||
"3964" => "JOHNSTON",
|
||||
"4030" => "JORDANIA",
|
||||
"4111" => "KIRIBATI",
|
||||
"1988" => "KUWEIT (ou Coveite)",
|
||||
"4200" => "LAOS",
|
||||
"4260" => "LESOTO",
|
||||
"4278" => "LETONIA",
|
||||
"4316" => "LIBANO",
|
||||
"4340" => "LIBERIA",
|
||||
"4383" => "LIBIA",
|
||||
"4405" => "LIECHTENSTEIN",
|
||||
"4421" => "LITUANIA",
|
||||
"4456" => "LUXEMBURGO",
|
||||
"4472" => "MACAU",
|
||||
"4499" => "MACEDONIA",
|
||||
"4502" => "MADAGASCAR",
|
||||
"4553" => "MALASIA",
|
||||
"4588" => "MALAVI",
|
||||
"4618" => "MALDIVAS",
|
||||
"4642" => "MALI",
|
||||
"4677" => "MALTA",
|
||||
"3595" => "MAN, ILHA DE",
|
||||
"4723" => "MARIANAS DO NORTE",
|
||||
"4740" => "MARROCOS",
|
||||
"4766" => "MARSHALL, ILHAS",
|
||||
"4774" => "MARTINICA",
|
||||
"4855" => "MAURICIO",
|
||||
"4880" => "MAURITANIA",
|
||||
"4936" => "MEXICO",
|
||||
"0930" => "MIANMAR",
|
||||
"4995" => "MICRONESIA",
|
||||
"5053" => "MOCAMBIQUE",
|
||||
"4944" => "MOLDAVIA",
|
||||
"4952" => "MONACO",
|
||||
"4979" => "MONGOLIA",
|
||||
"4985" => "MONTENEGRO",
|
||||
"5010" => "MONTSERRAT",
|
||||
"5070" => "NAMIBIA",
|
||||
"5088" => "NAURU",
|
||||
"5177" => "NEPAL",
|
||||
"5215" => "NICARAGUA",
|
||||
"5258" => "NIGER",
|
||||
"5282" => "NIGERIA",
|
||||
"5312" => "NIUE",
|
||||
"5355" => "NORFOLK, ILHA",
|
||||
"5380" => "NORUEGA",
|
||||
"5428" => "NOVA CALEDONIA",
|
||||
"5487" => "NOVA ZELANDIA",
|
||||
"5568" => "OMA",
|
||||
"5665" => "PACIFICO, ILHAS DO (POSSESSAO DOS EUA)",
|
||||
"5754" => "PALAU",
|
||||
"5780" => "PALESTINA",
|
||||
"5800" => "PANAMA",
|
||||
"5452" => "PAPUA NOVA GUINE",
|
||||
"5762" => "PAQUISTAO",
|
||||
"5860" => "PARAGUAI",
|
||||
"5894" => "PERU",
|
||||
"5932" => "PITCAIRN",
|
||||
"5991" => "POLINESIA FRANCESA",
|
||||
"6033" => "POLONIA",
|
||||
"6114" => "PORTO RICO",
|
||||
"6076" => "PORTUGAL",
|
||||
"6238" => "QUENIA",
|
||||
"6254" => "QUIRGUISTAO",
|
||||
"6289" => "REINO UNIDO",
|
||||
"6408" => "REPUBLICA CENTRO-AFRICANA",
|
||||
"8885" => "REPUBLICA DEMOCRATICA DO CONGO",
|
||||
"6475" => "REPUBLICA DOMINICANA",
|
||||
"7919" => "REPUBLICA TCHECA",
|
||||
"6602" => "REUNIAO",
|
||||
"6700" => "ROMENIA",
|
||||
"6750" => "RUANDA",
|
||||
"6769" => "RUSSIA",
|
||||
"6858" => "SAARA OCIDENTAL",
|
||||
"6777" => "SALOMAO, ILHAS",
|
||||
"6904" => "SAMOA",
|
||||
"6912" => "SAMOA AMERICANA",
|
||||
"6971" => "SAN MARINO",
|
||||
"7102" => "SANTA HELENA",
|
||||
"7153" => "SANTA LUCIA",
|
||||
"6955" => "SAO CRISTOVAO E NEVES",
|
||||
"6980" => "SAO MARTINHO, ILHA DE (PARTE FRANCESA)",
|
||||
"6998" => "SAO MARTINHO, ILHA DE (PARTE HOLANDESA)",
|
||||
"7005" => "SAO PEDRO E MIQUELON",
|
||||
"7200" => "SAO TOME E PRINCIPE",
|
||||
"7056" => "SAO VICENTE E GRANADINAS",
|
||||
"7315" => "SEICHELES",
|
||||
"7285" => "SENEGAL",
|
||||
"7358" => "SERRA LEOA",
|
||||
"7370" => "SERVIA",
|
||||
"7447" => "SIRIA",
|
||||
"7480" => "SOMALIA",
|
||||
"7501" => "SRI LANKA",
|
||||
"7595" => "SUDAO",
|
||||
"7600" => "SUDÃO DO SUL",
|
||||
"7641" => "SUECIA",
|
||||
"7676" => "SUICA",
|
||||
"7706" => "SURINAME",
|
||||
"7552" => "SVALBARD E JAN MAYEN",
|
||||
"7722" => "TADJIQUISTAO",
|
||||
"7765" => "TAILANDIA",
|
||||
"1619" => "TAIWAN",
|
||||
"7803" => "TANZANIA",
|
||||
"7820" => "TERRITORIO BRITANICO OCEANO INDICO",
|
||||
"7951" => "TIMOR LESTE",
|
||||
"8001" => "TOGO",
|
||||
"8109" => "TONGA",
|
||||
"8052" => "TOQUELAU",
|
||||
"8150" => "TRINIDAD E TOBAGO",
|
||||
"8206" => "TUNISIA",
|
||||
"8230" => "TURCAS E CAICOS",
|
||||
"8249" => "TURCOMENISTAO",
|
||||
"8273" => "TURQUIA",
|
||||
"8281" => "TUVALU",
|
||||
"8311" => "UCRANIA",
|
||||
"8338" => "UGANDA",
|
||||
"8451" => "URUGUAI",
|
||||
"8478" => "UZBEQUISTAO",
|
||||
"5517" => "VANUATU",
|
||||
"8486" => "VATICANO",
|
||||
"8508" => "VENEZUELA",
|
||||
"8583" => "VIETNA",
|
||||
"8630" => "VIRGENS, ILHAS (BRITANICAS)",
|
||||
"8664" => "VIRGENS, ILHAS (EUA)",
|
||||
"8753" => "WALLIS E FUTUNA, ILHAS",
|
||||
"8907" => "ZAMBIA",
|
||||
"6653" => "ZIMBABUE"
|
||||
];
|
||||
|
||||
public static function getAll(): array {
|
||||
return self::PAISES;
|
||||
}
|
||||
|
||||
public static function getNomeByCod(string $codigo): ?string {
|
||||
return self::PAISES[$codigo] ?? null;
|
||||
}
|
||||
|
||||
public static function exists(string $codigo): bool {
|
||||
return isset(self::PAISES[$codigo]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Database;
|
||||
|
||||
use WorkbloomERP\Services\DBService;
|
||||
|
||||
class DBFactory {
|
||||
/**
|
||||
* Método para criação simplificada de instâncias do serviço de banco de dados.
|
||||
*
|
||||
* Este método implementa o padrão de projeto Factory, permitindo que o desenvolvedor obtenha
|
||||
* uma instância pronta do **DBService** sem a necessidade de gerenciar manualmente o operador `new`.
|
||||
* Sua principal vantagem é a flexibilidade para alternar entre diferentes conexões e esquemas
|
||||
* (schemas) de banco de dados através de parâmetros opcionais, facilitando a operação em
|
||||
* ambientes multi-banco ou arquiteturas multi-tenant.
|
||||
*
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ## Funcionalidades Principais
|
||||
* 1. **Abstração de Instanciação:** Centraliza a criação do objeto, permitindo futuras implementações de Singletons ou Service Locators sem quebrar o código cliente.
|
||||
* 2. **Configuração On-the-fly:** Permite definir a conexão (ex: 'mysql_prod', 'sqlite_test') e o schema de trabalho no momento da criação.
|
||||
* 3. **Fluidez:** Ideal para ser utilizado em chamadas encadeadas (ex: `DBService::make()->select(...)`), tornando o código mais legível e conciso.
|
||||
*
|
||||
* ---
|
||||
* ## Exemplos de Uso
|
||||
* - **Conexão Padrão:** `DBService::make();` (Utiliza as definições default da classe).
|
||||
* - **Esquema Específico:** `DBService::make('', 'vendas');` (Mantém a conexão padrão, mas aponta para o schema de vendas).
|
||||
* - **Conexão Externa:** `DBService::make('external_db', 'public');`
|
||||
*
|
||||
* @param string $connection Identificador da conexão configurada no sistema.
|
||||
* @param string $schema Nome do esquema de banco de dados a ser selecionado.
|
||||
* @return DBService Uma nova instância do serviço de banco de dados configurada.
|
||||
*/
|
||||
public static function make(string $connection = 'DEFAULT', string $schema = 'shared'): DBService {
|
||||
return new DBService(connection: $connection, schema: $schema);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class AppException extends Exception {
|
||||
protected array $details = [];
|
||||
public function __construct(string $message, int $code = 500, array $details = [], ?Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->details = $details;
|
||||
}
|
||||
|
||||
public function getDetails(): array {
|
||||
return $this->details;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Services;
|
||||
|
||||
use KrothiumAPI\Database\RedisManager;
|
||||
|
||||
class CacheService extends RedisManager {
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Services;
|
||||
|
||||
use Throwable;
|
||||
use KrothiumAPI\Database\DBManager;
|
||||
|
||||
class DBService {
|
||||
protected string $connection;
|
||||
protected ?string $schema;
|
||||
|
||||
public function __construct(string $connection = 'DEFAULT', ?string $schema = 'shared') {
|
||||
$this->schema = $schema;
|
||||
$this->connection = strtoupper(string: $connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executa dentro de transação
|
||||
*/
|
||||
public function transaction(callable $callback): mixed {
|
||||
DBManager::beginTransaction(connectionName: $this->connection, schema: $this->schema);
|
||||
try {
|
||||
$result = $callback($this);
|
||||
DBManager::commit(connectionName: $this->connection, schema: $this->schema);
|
||||
return $result;
|
||||
} catch (Throwable $e) {
|
||||
DBManager::rollback(connectionName: $this->connection, schema: $this->schema);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executa query sem retorno
|
||||
*/
|
||||
public function execute(string $sql, array $params = []): bool {
|
||||
return DBManager::execute(
|
||||
sql: $sql,
|
||||
params: $params,
|
||||
connectionName: $this->connection,
|
||||
schema: $this->schema
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna um único registro
|
||||
*/
|
||||
public function fetchOne(string $sql, array $params = []): ?array {
|
||||
return DBManager::fetchOne(
|
||||
sql: $sql,
|
||||
params: $params,
|
||||
connectionName: $this->connection,
|
||||
schema: $this->schema
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna múltiplos registros
|
||||
*/
|
||||
public function fetchAll(string $sql, array $params = []): array {
|
||||
return DBManager::fetchAll(
|
||||
sql: $sql,
|
||||
params: $params,
|
||||
connectionName: $this->connection,
|
||||
schema: $this->schema
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Último ID inserido (via PDO interno do driver)
|
||||
*/
|
||||
public function lastInsertId(): string {
|
||||
return DBManager::lastInsertId(
|
||||
connectionName: $this->connection,
|
||||
schema: $this->schema
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use WorkbloomERP\Services\CacheService;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
|
||||
class CacheUtil {
|
||||
public static function set(string $key, int $ttl, mixed $value): bool {
|
||||
return CacheService::set(
|
||||
key: $key,
|
||||
value: is_string($value) ? $value : json_encode(
|
||||
value: match (true) {
|
||||
is_array($value), is_object($value) => $value,
|
||||
default => throw new AppException(message: 'Valor para cache deve ser string, array ou objeto.', code: 400)
|
||||
},
|
||||
flags: JSON_UNESCAPED_UNICODE
|
||||
),
|
||||
ttl: max(-1, $ttl)
|
||||
);
|
||||
}
|
||||
|
||||
public static function get(string $key): ?array {
|
||||
$cachedValue = CacheService::get(key: $key);
|
||||
return $cachedValue ? json_decode($cachedValue, true) : null;
|
||||
}
|
||||
|
||||
public static function delete(array $keys): bool {
|
||||
return CacheService::del(keys: $keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use Exception;
|
||||
use JsonException;
|
||||
|
||||
class CryptoUtil {
|
||||
/**
|
||||
* Criptografa uma string.
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function encrypt(string $data): string {
|
||||
$algorithm = self::getAlgorithm();
|
||||
$key = self::getKey();
|
||||
|
||||
$ivLength = openssl_cipher_iv_length($algorithm);
|
||||
if ($ivLength === false) {
|
||||
throw new Exception('Unable to determine IV length');
|
||||
}
|
||||
|
||||
$iv = random_bytes($ivLength);
|
||||
$cipherText = openssl_encrypt(
|
||||
$data,
|
||||
$algorithm,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv,
|
||||
$tag
|
||||
);
|
||||
|
||||
if ($cipherText === false) {
|
||||
throw new Exception('Failed to encrypt data');
|
||||
}
|
||||
|
||||
return self::encodePayload([
|
||||
'iv' => base64_encode($iv),
|
||||
'tag' => base64_encode($tag),
|
||||
'value' => base64_encode($cipherText)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptografa uma string.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function decrypt(string $payload): string {
|
||||
$algorithm = self::getAlgorithm();
|
||||
$key = self::getKey();
|
||||
|
||||
$decodedPayload = self::decodePayload($payload);
|
||||
|
||||
$plainText = openssl_decrypt(
|
||||
base64_decode($decodedPayload['value']),
|
||||
$algorithm,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
base64_decode($decodedPayload['iv']),
|
||||
base64_decode($decodedPayload['tag'])
|
||||
);
|
||||
if ($plainText === false) {
|
||||
throw new Exception('Failed to decrypt data');
|
||||
}
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna o algoritmo configurado.
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getAlgorithm(): string {
|
||||
$algorithm = $_ENV['SYSTEM_CRYPTO_ALGO'];
|
||||
if (!in_array($algorithm, openssl_get_cipher_methods(), true)) {
|
||||
throw new Exception('Invalid cipher algorithm');
|
||||
}
|
||||
return $algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna a chave de criptografia.
|
||||
*
|
||||
* A chave deve ser uma string hexadecimal de 64 caracteres (32 bytes).
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getKey(): string {
|
||||
$hexKey = $_ENV['SYSTEM_CRYPTO_KEY'];
|
||||
if (empty($hexKey)) {
|
||||
throw new Exception('Encryption key not configured');
|
||||
}
|
||||
|
||||
$key = hex2bin($hexKey);
|
||||
if ($key === false || strlen($key) !== 32) {
|
||||
throw new Exception(message: 'Invalid encryption key. Expected 32 bytes (64 hex characters).');
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Codifica o payload para armazenamento.
|
||||
*
|
||||
* @param array $payload
|
||||
* @return string
|
||||
* @throws JsonException
|
||||
*/
|
||||
private static function encodePayload(array $payload): string {
|
||||
return base64_encode(
|
||||
json_encode(
|
||||
$payload,
|
||||
JSON_THROW_ON_ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodifica o payload criptografado.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function decodePayload(string $payload): array {
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
base64_decode($payload),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (JsonException $e) {
|
||||
throw new Exception(message: 'Invalid encrypted payload', previous: $e);
|
||||
}
|
||||
|
||||
foreach (['iv', 'tag', 'value'] as $field) {
|
||||
if (!isset($decoded[$field])) {
|
||||
throw new Exception(sprintf('Missing "%s" field in encrypted payload', $field));
|
||||
}
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CypherUtil {
|
||||
public static function hash(string $data): string {
|
||||
if (!isset($_ENV['SYSTEM_CYPHER_ALGO']) || !isset($_ENV['SYSTEM_CYPHER_PEPPER'])) {
|
||||
throw new Exception('Missing required environment variables for hashing');
|
||||
}
|
||||
return hash_hmac(algo: $_ENV['SYSTEM_CYPHER_ALGO'], data: $data, key: $_ENV['SYSTEM_CYPHER_PEPPER']);
|
||||
}
|
||||
|
||||
public static function verify(string $data, string $hash): bool {
|
||||
return hash_equals(known_string: self::hash($data), user_string: $hash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use Exception;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use DateTimeImmutable;
|
||||
|
||||
class JwtUtil {
|
||||
private int $ttl;
|
||||
private string $issuer;
|
||||
private string $secretKey;
|
||||
private string $algorithm;
|
||||
|
||||
public function __construct(string $secretKey, string $algorithm = 'HS256', int $ttl = 3600, string $issuer = 'workbloomerp') {
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception(message: 'JWT secret não configurado.');
|
||||
}
|
||||
$this->ttl = $ttl;
|
||||
$this->secretKey = $secretKey;
|
||||
$this->algorithm = $algorithm;
|
||||
$this->issuer = $issuer;
|
||||
}
|
||||
|
||||
public function generate(array $payload): string {
|
||||
$defaultClaims = [];
|
||||
$defaultClaims['iat'] = $payload['iat'] ?? (new DateTimeImmutable())->getTimestamp();
|
||||
$defaultClaims['exp'] = $payload['exp'] ?? ((new DateTimeImmutable())->modify("+{$this->ttl} seconds")->getTimestamp());
|
||||
|
||||
$finalPayload = array_merge($payload, $defaultClaims);
|
||||
return JWT::encode(
|
||||
payload: $finalPayload,
|
||||
key: $this->secretKey,
|
||||
alg: $this->algorithm
|
||||
);
|
||||
}
|
||||
|
||||
public function validate(string $token): object {
|
||||
return JWT::decode(
|
||||
jwt: $token,
|
||||
keyOrKeyArray: new Key(
|
||||
keyMaterial: $this->secretKey,
|
||||
algorithm: $this->algorithm
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
class SanitizeUtil {
|
||||
public static function string(mixed $value): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
$value = trim(string: $value);
|
||||
$value = strip_tags(string: $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function email(mixed $value): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
$value = filter_var(value: $value, filter: FILTER_SANITIZE_EMAIL);
|
||||
return $value ?: '';
|
||||
}
|
||||
|
||||
public static function int(mixed $value): ?int {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
return (int) filter_var(value: $value, filter: FILTER_SANITIZE_NUMBER_INT);
|
||||
}
|
||||
|
||||
public static function float(mixed $value): ?float {
|
||||
if($value === null) {
|
||||
return null;
|
||||
}
|
||||
return (float) filter_var(
|
||||
value: $value,
|
||||
filter: FILTER_SANITIZE_NUMBER_FLOAT,
|
||||
options: FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND
|
||||
);
|
||||
}
|
||||
|
||||
public static function document(mixed $value): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
// remove espaços
|
||||
$value = trim($value);
|
||||
// converte caracteres especiais (Ç -> C, Á -> A, etc.)
|
||||
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
|
||||
// mantém apenas letras e números
|
||||
$value = preg_replace('/[^a-zA-Z0-9]/', '', $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function boolean(mixed $value): ?bool {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$value = strtolower($value);
|
||||
if (in_array($value, ['true', '1', 'yes'], true)) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($value, ['false', '0', 'no'], true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (is_int($value)) {
|
||||
return $value === 1;
|
||||
}
|
||||
// Se não for possível converter, retorna null
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function phone(mixed $value, bool $withCountryCode = false): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
// remove tudo que não for número
|
||||
$value = preg_replace('/\D/', '', $value);
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
// adiciona DDI do Brasil se não tiver
|
||||
if ($withCountryCode) {
|
||||
if (strlen($value) === 10 || strlen($value) === 11) {
|
||||
$value = "55{$value}";
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
class ValidateUtil {
|
||||
public static function cpf(?string $cpf): bool {
|
||||
if ($cpf == null) {
|
||||
return false;
|
||||
}
|
||||
// Remove tudo que não for número
|
||||
$cpf = preg_replace('/\D/', '', $cpf);
|
||||
// Precisa ter 11 dígitos
|
||||
if (strlen($cpf) !== 11) {
|
||||
return false;
|
||||
}
|
||||
// Bloqueia CPFs com todos os dígitos iguais (11111111111, 00000000000, etc)
|
||||
if (preg_match('/^(\d)\1{10}$/', $cpf)) {
|
||||
return false;
|
||||
}
|
||||
// Valida primeiro dígito verificador
|
||||
for ($t = 9; $t < 11; $t++) {
|
||||
$soma = 0;
|
||||
for ($i = 0; $i < $t; $i++) {
|
||||
$soma += $cpf[$i] * (($t + 1) - $i);
|
||||
}
|
||||
$digito = ((10 * $soma) % 11) % 10;
|
||||
if ($cpf[$t] != $digito) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cnpj(?string $cnpj): bool {
|
||||
if ($cnpj == null) {
|
||||
return false;
|
||||
}
|
||||
// Remove máscara
|
||||
$cnpj = strtoupper(preg_replace('/[^A-Z0-9]/', '', $cnpj));
|
||||
// Tamanho fixo
|
||||
if (strlen($cnpj) !== 14) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bloqueia CNPJs com todos os dígitos iguais (11111111111111, 00000000000000, etc)
|
||||
if (preg_match('/^(\d)\1{13}$/', $cnpj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Regex: 12 alfanum + 2 numéricos
|
||||
if (!preg_match('/^[A-Z0-9]{12}[0-9]{2}$/', $cnpj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converte para valores numéricos
|
||||
$valores = [];
|
||||
for ($i = 0; $i < 14; $i++) {
|
||||
$char = $cnpj[$i];
|
||||
if (ctype_digit($char)) {
|
||||
$valores[$i] = (int)$char;
|
||||
} else {
|
||||
$valores[$i] = ord($char) - 48;
|
||||
}
|
||||
}
|
||||
|
||||
// === Primeiro DV ===
|
||||
$pesos1 = [5,4,3,2,9,8,7,6,5,4,3,2];
|
||||
$soma = 0;
|
||||
for ($i = 0; $i < 12; $i++) {
|
||||
$soma += $valores[$i] * $pesos1[$i];
|
||||
}
|
||||
|
||||
$resto = $soma % 11;
|
||||
$dv1 = ($resto < 2) ? 0 : 11 - $resto;
|
||||
if ($dv1 !== $valores[12]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// === Segundo DV ===
|
||||
$pesos2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];
|
||||
$soma = 0;
|
||||
for ($i = 0; $i < 13; $i++) {
|
||||
$soma += $valores[$i] * $pesos2[$i];
|
||||
}
|
||||
|
||||
$resto = $soma % 11;
|
||||
$dv2 = ($resto < 2) ? 0 : 11 - $resto;
|
||||
return $dv2 === $valores[13];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user