total_purchases - $client->lifetime_returns; // Buscar el tier apropiado ordenado por monto mínimo descendente $newTier = ClientTier::active() ->where('min_purchase_amount', '<=', $netPurchases) ->where(function ($query) use ($netPurchases) { $query->whereNull('max_purchase_amount') ->orWhere('max_purchase_amount', '>=', $netPurchases); }) ->orderBy('min_purchase_amount', 'desc') ->first(); // Si cambió el tier, registrar en historial if ($newTier && $client->tier_id !== $newTier->id) { $this->recordTierChange($client, $newTier, $netPurchases); $client->tier_id = $newTier->id; $client->save(); } return $newTier; } /** * Registrar cambio de tier en el historial */ protected function recordTierChange(Client $client, ClientTier $newTier, float $totalAtChange): void { ClientTierHistory::create([ 'client_id' => $client->id, 'old_tier_id' => $client->tier_id, 'new_tier_id' => $newTier->id, 'total_at_change' => $totalAtChange, 'reason' => 'Actualización automática por cambio en compras acumuladas', ]); } /** * Obtener descuento aplicable para un cliente */ public function getApplicableDiscount(Client $client): float { if (!$client->tier) { return 0.00; } return $client->tier->discount_percentage; } /** * Actualizar estadísticas de compra del cliente */ public function updateClientPurchaseStats(Client $client, float $amount, int $quantity = 1): void { $client->total_purchases += $amount; $client->total_transactions += 1; $client->last_purchase_at = now(); $client->save(); // Recalcular tier después de actualizar stats $this->recalculateClientTier($client); } /** * Revertir estadísticas de compra del cliente (para devoluciones) */ public function revertClientPurchaseStats(Client $client, float $amount): void { $client->lifetime_returns += $amount; $client->save(); // Recalcular tier después de registrar devolución $this->recalculateClientTier($client); } /** * Obtener todos los tiers activos */ public function getActiveTiers() { return ClientTier::active() ->orderBy('min_purchase_amount') ->get(); } /** * Obtener tier apropiado para un monto específico */ public function getTierForAmount(float $amount): ?ClientTier { return ClientTier::active() ->where('min_purchase_amount', '<=', $amount) ->where(function ($query) use ($amount) { $query->whereNull('max_purchase_amount') ->orWhere('max_purchase_amount', '>=', $amount); }) ->orderBy('min_purchase_amount', 'desc') ->first(); } /** * Obtener estadísticas del cliente */ public function getClientStats(Client $client): array { return [ 'client_number' => $client->client_number, 'total_purchases' => $client->total_purchases, 'lifetime_returns' => $client->lifetime_returns, 'net_purchases' => $client->net_purchases, 'total_transactions' => $client->total_transactions, 'last_purchase_at' => $client->last_purchase_at, 'average_purchase' => $client->total_transactions > 0 ? $client->total_purchases / $client->total_transactions : 0, 'current_tier' => $client->tier ? [ 'id' => $client->tier->id, 'name' => $client->tier->tier_name, 'discount' => $client->tier->discount_percentage, ] : null, 'next_tier' => $this->getNextTier($client), ]; } /** * Obtener información del siguiente tier disponible */ protected function getNextTier(Client $client): ?array { $currentAmount = $client->net_purchases; $currentTierId = $client->tier_id; $nextTier = ClientTier::active() ->where('min_purchase_amount', '>', $currentAmount) ->orderBy('min_purchase_amount') ->first(); if ($nextTier) { return [ 'id' => $nextTier->id, 'name' => $nextTier->tier_name, 'discount' => $nextTier->discount_percentage, 'required_amount' => $nextTier->min_purchase_amount, 'remaining_amount' => $nextTier->min_purchase_amount - $currentAmount, ]; } return null; } }