From 39d1b3aee5d3a07222fee44db4d53d6f39552446 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Tue, 25 Nov 2025 15:22:03 -0600 Subject: [PATCH] =?UTF-8?q?FEAT:=20Mejorar=20la=20importaci=C3=B3n=20de=20?= =?UTF-8?q?datos=20y=20evitar=20duplicados=20en=20ventas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Netbien/SimCardController.php | 152 ++++++++---------- 1 file changed, 68 insertions(+), 84 deletions(-) diff --git a/app/Http/Controllers/Netbien/SimCardController.php b/app/Http/Controllers/Netbien/SimCardController.php index 2e8043e..a65c8fd 100644 --- a/app/Http/Controllers/Netbien/SimCardController.php +++ b/app/Http/Controllers/Netbien/SimCardController.php @@ -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'); + } } }