- Removido README.md padrão do Laravel (backend) - Removidos scripts de deploy (não mais necessários) - Atualizado copilot-instructions.md para novo fluxo - Adicionada documentação de auditoria do servidor - Sincronizado código de produção com repositório Novo workflow: - Trabalhamos diretamente em /root/webmoney (symlink para /var/www/webmoney) - Mudanças PHP são instantâneas - Mudanças React requerem 'npm run build' - Commit após validação funcional
368 lines
11 KiB
PHP
Executable File
368 lines
11 KiB
PHP
Executable File
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||
|
||
class BusinessSetting extends Model
|
||
{
|
||
use HasFactory;
|
||
|
||
protected $fillable = [
|
||
'user_id',
|
||
'currency',
|
||
'name',
|
||
'business_type',
|
||
'employees_count',
|
||
'hours_per_week',
|
||
'working_days_per_week',
|
||
'working_days_per_month',
|
||
'productivity_rate',
|
||
'monthly_revenue',
|
||
'fixed_expenses',
|
||
'tax_rate',
|
||
'price_includes_tax',
|
||
'vat_rate',
|
||
'sales_commission',
|
||
'card_fee',
|
||
'other_variable_costs',
|
||
'investment_rate',
|
||
'profit_margin',
|
||
'markup_factor',
|
||
'is_active',
|
||
];
|
||
|
||
protected $casts = [
|
||
'business_type' => 'string',
|
||
'employees_count' => 'integer',
|
||
'hours_per_week' => 'decimal:2',
|
||
'working_days_per_week' => 'integer',
|
||
'working_days_per_month' => 'integer',
|
||
'productivity_rate' => 'decimal:2',
|
||
'monthly_revenue' => 'decimal:2',
|
||
'fixed_expenses' => 'decimal:2',
|
||
'tax_rate' => 'decimal:2',
|
||
'price_includes_tax' => 'boolean',
|
||
'vat_rate' => 'decimal:2',
|
||
'sales_commission' => 'decimal:2',
|
||
'card_fee' => 'decimal:2',
|
||
'other_variable_costs' => 'decimal:2',
|
||
'investment_rate' => 'decimal:2',
|
||
'profit_margin' => 'decimal:2',
|
||
'markup_factor' => 'decimal:4',
|
||
'is_active' => 'boolean',
|
||
];
|
||
|
||
/**
|
||
* Derived attributes - calculated from hours_per_week
|
||
*/
|
||
protected $appends = ['hours_per_day', 'productive_hours', 'fixed_cost_per_hour'];
|
||
|
||
/**
|
||
* Calcula horas por dia a partir de horas por semana
|
||
* hours_per_day = hours_per_week / working_days_per_week
|
||
*/
|
||
public function getHoursPerDayAttribute(): float
|
||
{
|
||
$hoursPerWeek = (float) ($this->hours_per_week ?? 40);
|
||
$daysPerWeek = (int) ($this->working_days_per_week ?? 5);
|
||
|
||
return $daysPerWeek > 0 ? round($hoursPerWeek / $daysPerWeek, 2) : 8;
|
||
}
|
||
|
||
/**
|
||
* Relacionamento com usuário
|
||
*/
|
||
public function user(): BelongsTo
|
||
{
|
||
return $this->belongsTo(User::class);
|
||
}
|
||
|
||
/**
|
||
* Fichas técnicas de produtos que usam esta configuração
|
||
*/
|
||
public function productSheets(): HasMany
|
||
{
|
||
return $this->hasMany(ProductSheet::class);
|
||
}
|
||
|
||
/**
|
||
* Fichas técnicas de serviços que usam esta configuração
|
||
*/
|
||
public function serviceSheets(): HasMany
|
||
{
|
||
return $this->hasMany(ServiceSheet::class);
|
||
}
|
||
|
||
/**
|
||
* Calcula as horas produtivas mensais
|
||
*
|
||
* Formula baseada em horas por semana:
|
||
* Horas Produtivas = Funcionários × (Horas/Semana × 4.33 semanas/mês) × Produtividade%
|
||
*
|
||
* Ou usando dias do mês:
|
||
* Horas Produtivas = Funcionários × (Horas/Semana / Dias/Semana × Dias/Mês) × Produtividade%
|
||
*
|
||
* @return float
|
||
*/
|
||
public function getProductiveHoursAttribute(): float
|
||
{
|
||
$employees = (int) ($this->employees_count ?? 1);
|
||
$hoursPerWeek = (float) ($this->hours_per_week ?? 40);
|
||
$daysPerWeek = (int) ($this->working_days_per_week ?? 5);
|
||
$daysPerMonth = (int) ($this->working_days_per_month ?? 22);
|
||
$productivity = (float) ($this->productivity_rate ?? 80) / 100;
|
||
|
||
// Calcula horas por dia a partir de horas por semana
|
||
$hoursPerDay = $daysPerWeek > 0 ? $hoursPerWeek / $daysPerWeek : 8;
|
||
|
||
// Horas produtivas = funcionários × horas/dia × dias/mês × produtividade
|
||
return round($employees * $hoursPerDay * $daysPerMonth * $productivity, 2);
|
||
}
|
||
|
||
/**
|
||
* Calcula o gasto fixo por hora
|
||
* Gasto Fixo/Hora = Despesas Fixas / Horas Produtivas
|
||
*
|
||
* @return float
|
||
*/
|
||
public function getFixedCostPerHourAttribute(): float
|
||
{
|
||
$productiveHours = $this->productive_hours;
|
||
|
||
if ($productiveHours <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
return round((float) $this->fixed_expenses / $productiveHours, 2);
|
||
}
|
||
|
||
/**
|
||
* Calcula o gasto fixo por minuto
|
||
*
|
||
* @return float
|
||
*/
|
||
public function getFixedCostPerMinuteAttribute(): float
|
||
{
|
||
return round($this->fixed_cost_per_hour / 60, 4);
|
||
}
|
||
|
||
/**
|
||
* Calcula o preço de venda de um serviço
|
||
* Preço = (Gasto Fixo/Hora × Duração em Horas + CSV) × Markup
|
||
*
|
||
* Para B2C, ainda adiciona o IVA no final
|
||
*
|
||
* @param float $durationMinutes Duração do serviço em minutos
|
||
* @param float $csv Custo do Serviço Vendido (insumos)
|
||
* @return float
|
||
*/
|
||
public function calculateServicePrice(float $durationMinutes, float $csv): float
|
||
{
|
||
$markup = $this->markup_factor ?? $this->calculateMarkup();
|
||
|
||
if ($markup <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
// Gasto fixo proporcional ao tempo do serviço
|
||
$fixedCostPortion = $this->fixed_cost_per_minute * $durationMinutes;
|
||
|
||
// Preço base = (Gasto Fixo + CSV) × Markup
|
||
$priceWithoutTax = ($fixedCostPortion + $csv) * $markup;
|
||
|
||
// Se B2C, adiciona IVA
|
||
if ($this->price_includes_tax) {
|
||
$vatRate = (float) ($this->vat_rate ?? 0);
|
||
return round($priceWithoutTax * (1 + $vatRate / 100), 2);
|
||
}
|
||
|
||
return round($priceWithoutTax, 2);
|
||
}
|
||
|
||
/**
|
||
* Calcula o percentual de despesas fixas sobre a receita
|
||
*
|
||
* @return float
|
||
*/
|
||
public function getFixedExpensesRateAttribute(): float
|
||
{
|
||
if ($this->monthly_revenue <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
return round(($this->fixed_expenses / $this->monthly_revenue) * 100, 2);
|
||
}
|
||
|
||
/**
|
||
* Calcula o total de custos variáveis (sem CMV)
|
||
* Para B2C (price_includes_tax=true), o tax_rate NÃO entra aqui pois é adicionado ao final
|
||
* Para B2B (price_includes_tax=false), o tax_rate é uma dedução do preço
|
||
*
|
||
* @return float
|
||
*/
|
||
public function getTotalVariableCostsAttribute(): float
|
||
{
|
||
$costs = $this->sales_commission +
|
||
$this->card_fee +
|
||
$this->other_variable_costs;
|
||
|
||
// Se B2B (preço NÃO inclui imposto), adiciona tax_rate como custo
|
||
if (!$this->price_includes_tax) {
|
||
$costs += $this->tax_rate;
|
||
}
|
||
|
||
return $costs;
|
||
}
|
||
|
||
/**
|
||
* Calcula o fator de Markup usando a fórmula:
|
||
* Markup = 1 / (1 - (Despesas Fixas % + Custos Variáveis % + Investimento % + Lucro %))
|
||
*
|
||
* @return float
|
||
*/
|
||
public function calculateMarkup(): float
|
||
{
|
||
// Converter percentuais para decimais
|
||
$fixedExpensesRate = $this->fixed_expenses_rate / 100;
|
||
$variableCosts = $this->total_variable_costs / 100;
|
||
$investmentRate = $this->investment_rate / 100;
|
||
$profitMargin = $this->profit_margin / 100;
|
||
|
||
// Total de deduções
|
||
$totalDeductions = $fixedExpensesRate + $variableCosts + $investmentRate + $profitMargin;
|
||
|
||
// Verificar se é possível calcular (não pode ser >= 100%)
|
||
if ($totalDeductions >= 1) {
|
||
return 0; // Markup inválido - deduções >= 100%
|
||
}
|
||
|
||
// Fórmula: Markup = 1 / (1 - deduções)
|
||
$markup = 1 / (1 - $totalDeductions);
|
||
|
||
return round($markup, 4);
|
||
}
|
||
|
||
/**
|
||
* Recalcula e salva o markup
|
||
*
|
||
* @return float
|
||
*/
|
||
public function recalculateMarkup(): float
|
||
{
|
||
$this->markup_factor = $this->calculateMarkup();
|
||
$this->save();
|
||
|
||
return $this->markup_factor;
|
||
}
|
||
|
||
/**
|
||
* Calcula o preço de venda para um CMV dado
|
||
*
|
||
* B2B (price_includes_tax=false): Preço = CMV × Markup (imposto já está nas deduções)
|
||
* B2C (price_includes_tax=true): Preço = CMV × Markup × (1 + VAT%) (imposto adicionado ao final)
|
||
*
|
||
* @param float $cmv Custo da Mercadoria Vendida
|
||
* @return float
|
||
*/
|
||
public function calculateSalePrice(float $cmv): float
|
||
{
|
||
$markup = $this->markup_factor ?? $this->calculateMarkup();
|
||
|
||
if ($markup <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
$priceWithoutTax = $cmv * $markup;
|
||
|
||
// Se B2C (preço inclui imposto), adiciona o IVA/VAT ao preço final
|
||
if ($this->price_includes_tax) {
|
||
$vatRate = (float) ($this->vat_rate ?? 0);
|
||
return round($priceWithoutTax * (1 + $vatRate / 100), 2);
|
||
}
|
||
|
||
return round($priceWithoutTax, 2);
|
||
}
|
||
|
||
/**
|
||
* Calcula o preço SEM impostos para um CMV dado
|
||
* Útil para mostrar o preço base antes do IVA
|
||
*
|
||
* @param float $cmv Custo da Mercadoria Vendida
|
||
* @return float
|
||
*/
|
||
public function calculatePriceWithoutTax(float $cmv): float
|
||
{
|
||
$markup = $this->markup_factor ?? $this->calculateMarkup();
|
||
|
||
if ($markup <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
return round($cmv * $markup, 2);
|
||
}
|
||
|
||
/**
|
||
* Extrai o valor do IVA de um preço final (B2C)
|
||
*
|
||
* @param float $finalPrice Preço final com IVA
|
||
* @return float
|
||
*/
|
||
public function extractVat(float $finalPrice): float
|
||
{
|
||
if (!$this->price_includes_tax) {
|
||
return 0;
|
||
}
|
||
|
||
$vatRate = (float) ($this->vat_rate ?? 0);
|
||
if ($vatRate <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
$priceWithoutVat = $finalPrice / (1 + $vatRate / 100);
|
||
return round($finalPrice - $priceWithoutVat, 2);
|
||
}
|
||
|
||
/**
|
||
* Retorna o breakdown do Markup para exibição
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getMarkupBreakdownAttribute(): array
|
||
{
|
||
return [
|
||
'fixed_expenses_rate' => $this->fixed_expenses_rate,
|
||
'tax_rate' => (float) $this->tax_rate,
|
||
'price_includes_tax' => (bool) $this->price_includes_tax,
|
||
'vat_rate' => (float) ($this->vat_rate ?? 21),
|
||
'sales_commission' => (float) $this->sales_commission,
|
||
'card_fee' => (float) $this->card_fee,
|
||
'other_variable_costs' => (float) $this->other_variable_costs,
|
||
'total_variable_costs' => $this->total_variable_costs,
|
||
'investment_rate' => (float) $this->investment_rate,
|
||
'profit_margin' => (float) $this->profit_margin,
|
||
'total_deductions' => $this->fixed_expenses_rate + $this->total_variable_costs + $this->investment_rate + $this->profit_margin,
|
||
'markup_factor' => (float) ($this->markup_factor ?? $this->calculateMarkup()),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Scope para buscar configurações ativas do usuário
|
||
*/
|
||
public function scopeOfUser($query, $userId)
|
||
{
|
||
return $query->where('user_id', $userId);
|
||
}
|
||
|
||
/**
|
||
* Scope para buscar apenas configurações ativas
|
||
*/
|
||
public function scopeActive($query)
|
||
{
|
||
return $query->where('is_active', true);
|
||
}
|
||
}
|