pdv.backend/app/Services/ClientTierService.php

179 lines
5.5 KiB
PHP

<?php
namespace App\Services;
use App\Models\Client;
use App\Models\ClientTier;
use App\Models\ClientTierHistory;
use Illuminate\Support\Facades\DB;
class ClientTierService
{
/**
* Generar número de cliente único secuencial
*/
public function generateClientNumber(): string
{
$lastClient = Client::orderBy('id', 'desc')->first();
$nextNumber = $lastClient ? ((int) substr($lastClient->client_number, 4)) + 1 : 1;
return 'CLI-' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT);
}
/**
* Calcular y actualizar el tier del cliente basado en sus compras totales
*/
public function recalculateClientTier(Client $client): ?ClientTier
{
// Calcular compras netas (total - devoluciones)
$netPurchases = $client->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;
}
}