179 lines
5.5 KiB
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;
|
|
}
|
|
}
|