'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); } }