FEAT: Mejorar la importación de datos y evitar duplicados en ventas

This commit is contained in:
Juan Felipe Zapata Moreno 2025-11-25 15:22:03 -06:00
parent f070777945
commit 39d1b3aee5

View File

@ -19,6 +19,7 @@
use Illuminate\Http\Request;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Notsoweb\ApiResponse\Enums\ApiResponse;
/**
@ -131,61 +132,54 @@ public function import(Request $request)
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray();
DB::beginTransaction();
// Leer encabezados de la primera fila
$this->buildColumnMap($rows[0]);
try {
// Leer encabezados de la primera fila
$this->buildColumnMap($rows[0]);
foreach ($rows as $index => $row) {
if ($index === 0) continue; // Saltar encabezados
foreach ($rows as $index => $row) {
if ($index === 0) continue; // Saltar encabezados
// Iniciar transacción por fila
DB::beginTransaction();
try {
$this->processRow([
try {
$this->processRow([
'iccid' => $this->getColumnValue($row, 'ICCID'),
'msisdn' => $this->getColumnValue($row, 'MSISDN'),
'paquetes' => $this->getColumnValue($row, 'PAQUETES'),
'usuario' => $this->getColumnValue($row, 'USUARIO'),
'fecha_venta' => $this->getColumnValue($row, 'FECHA_VENTA'),
'metodo_pago' => $this->getColumnValue($row, 'METODO_PAGO'),
], $index + 1);
// Commit después de cada fila exitosa
DB::commit();
} catch (\Exception $e) {
// Revertir solo esta fila
DB::rollBack();
// Agregar error pero continuar con las demás filas
$this->stats['errors'][] = [
'fila' => $index + 1,
'error' => $e->getMessage(),
'datos' => [
'iccid' => $this->getColumnValue($row, 'ICCID'),
'msisdn' => $this->getColumnValue($row, 'MSISDN'),
'paquetes' => $this->getColumnValue($row, 'PAQUETES'),
'usuario' => $this->getColumnValue($row, 'USUARIO'),
'fecha_venta' => $this->getColumnValue($row, 'FECHA_VENTA'),
'metodo_pago' => $this->getColumnValue($row, 'METODO_PAGO'),
], $index + 1);
} catch (\Exception $e) {
// Capturar información detallada del error antes de revertir
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'success' => false,
'message' => 'Error en la importación',
'error' => $e->getMessage(),
'fila' => $index + 1,
'datos_fila' => [
'iccid' => $this->getColumnValue($row, 'ICCID'),
'msisdn' => $this->getColumnValue($row, 'MSISDN'),
'paquetes' => $this->getColumnValue($row, 'PAQUETES'),
'usuario' => $this->getColumnValue($row, 'USUARIO'),
'fecha_venta' => $this->getColumnValue($row, 'FECHA_VENTA'),
'metodo_pago' => $this->getColumnValue($row, 'METODO_PAGO'),
],
'stats' => $this->stats,
], 500);
}
]
];
}
DB::commit();
return ApiResponse::OK->response([
'success' => true,
'message' => 'Importación completada',
'stats' => $this->stats,
'packages_created' => array_values(array_map(fn($p) => [
'name' => $p->name,
'price' => $p->price
], $this->packageCache)),
]);
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
return ApiResponse::OK->response([
'success' => true,
'message' => 'Importación completada',
'stats' => $this->stats,
'packages_created' => array_values(array_map(fn($p) => [
'name' => $p->name,
'price' => $p->price
], $this->packageCache)),
]);
} catch (\Exception $e) {
return ApiResponse::BAD_REQUEST->response([
'success' => false,
@ -267,6 +261,16 @@ private function processSale(SimCard $sim, array $row)
$packageInfo['price']
);
//Evitar duplicados
$existingSaleItem = SaleItem::where('sim_card_id', $sim->id)
->where('package_id', $package->id)
->first();
if ($existingSaleItem) {
// Ya existe una venta para esta SIM+Paquete, salir
return;
}
// Buscar o crear el cliente
$usuario = trim($row['usuario'] ?? '');
$client = Client::where('full_name', $usuario)->first()
@ -297,7 +301,7 @@ private function processSale(SimCard $sim, array $row)
// Crear la venta
$sale = Sale::create([
'client_id' => $client->id,
'cash_close_id' => null, // Se dejará null para importaciones
'cash_close_id' => null,
'total_amount' => $package->price,
'payment_method' => $paymentMethod,
'sale_date' => $saleDate,
@ -325,37 +329,7 @@ private function processSale(SimCard $sim, array $row)
]);
}
// Verificar si ya existe una venta para esta SIM con este paquete
// (para evitar duplicados en reimportaciones)
$existingSale = Sale::whereHas('saleItems', function ($query) use ($sim, $package) {
$query->where('sim_card_id', $sim->id)
->where('package_id', $package->id);
})->where('client_id', $client->id)
->where('sale_date', $saleDate)
->exists();
if (!$existingSale) {
// Crear la venta
$sale = Sale::create([
'client_id' => $client->id,
'cash_close_id' => null, // Importaciones no tienen corte de caja
'total_amount' => $package->price,
'payment_method' => $paymentMethod,
'sale_date' => $saleDate,
]);
// Crear el item de venta
SaleItem::create([
'sale_id' => $sale->id,
'sim_card_id' => $sim->id,
'package_id' => $package->id,
]);
$this->stats['sales_created']++;
}
// Asignar paquete a la SIM (usando syncWithoutDetaching para evitar duplicados)
// Primero verificamos si ya existe la relación activa
// Asignar paquete a la SIM
$hasActivePackage = $sim->packages()
->wherePivot('package_id', $package->id)
->wherePivot('is_active', true)
@ -371,30 +345,40 @@ private function processSale(SimCard $sim, array $row)
// Actualizar status de la SIM
$sim->update(['status' => SimCardStatus::ASSIGNED]);
// stats
$this->stats['sales_created']++;
$this->stats['assigned']++;
}
private function parseSaleDate($dateValue)
{
if (empty($dateValue)) {
return now();
return now()->startOfDay()->format('Y-m-d H:i:s');
}
// Si es numérico
if (is_numeric($dateValue)) {
try {
$excelBaseDate = new \DateTime('1899-12-30');
$excelBaseDate->modify("+{$dateValue} days");
return $excelBaseDate->format('Y-m-d H:i:s');
return $excelBaseDate->format('Y-m-d') . ' 00:00:00';
} catch (\Exception $e) {
return now();
return now()->startOfDay()->format('Y-m-d H:i:s');
}
}
// Intentar parsear como fecha
// Si es string, intentar parsear como DD/MM/YYYY
try {
return \Carbon\Carbon::parse($dateValue)->format('Y-m-d H:i:s');
// Primero intentar formato DD/MM/YYYY explícitamente
$date = \Carbon\Carbon::createFromFormat('d/m/Y', $dateValue);
return $date->startOfDay()->format('Y-m-d H:i:s');
} catch (\Exception $e) {
return now();
// Fallback: intentar parsear automáticamente
try {
return \Carbon\Carbon::parse($dateValue)->startOfDay()->format('Y-m-d H:i:s');
} catch (\Exception $e) {
return now()->startOfDay()->format('Y-m-d H:i:s');
}
}
}