591 lines
18 KiB
PHP
591 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Netbien;
|
|
|
|
/**
|
|
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
|
*/
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Netbien\SimCardStoreRequest;
|
|
use App\Http\Requests\Netbien\SimCardUpdateRequest;
|
|
use App\Models\SimCard;
|
|
use App\Models\Client;
|
|
use App\Models\ClientSim;
|
|
use App\Models\Packages;
|
|
use App\Models\Sale;
|
|
use App\Models\SaleItem;
|
|
use App\Enums\SimCardStatus;
|
|
use Illuminate\Http\Request;
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class SimCardController extends Controller
|
|
{
|
|
public function index()
|
|
{
|
|
$simCards = SimCard::with('packSims.package:id,name')->orderBy('id', 'asc')->paginate(config('app.pagination'));
|
|
|
|
return ApiResponse::OK->response([
|
|
'data' => $simCards,
|
|
]);
|
|
}
|
|
|
|
public function show(SimCard $simCard)
|
|
{
|
|
$simCard->load('packSims.package:id,name');
|
|
|
|
return ApiResponse::OK->response([
|
|
'data' => $simCard,
|
|
]);
|
|
}
|
|
|
|
public function store(SimCardStoreRequest $request)
|
|
{
|
|
try {
|
|
DB::beginTransaction();
|
|
|
|
$simCard = SimCard::create($request->validated());
|
|
|
|
if ($request->has('package_id')) {
|
|
// Asignar el paquete con fecha de activación
|
|
$simCard->packages()->attach($request->package_id, [
|
|
'activated_at' => now(),
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
$simCard->load('activePackage');
|
|
|
|
return ApiResponse::CREATED->response([
|
|
'data' => $simCard,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al crear SIM card',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function update(SimCardUpdateRequest $request, SimCard $simCard)
|
|
{
|
|
$simCard->update($request->validated());
|
|
|
|
return ApiResponse::OK->response([
|
|
'data' => $simCard,
|
|
]);
|
|
}
|
|
|
|
public function destroy(SimCard $simCard)
|
|
{
|
|
try {
|
|
DB::beginTransaction();
|
|
|
|
$simCard->delete();
|
|
|
|
DB::commit();
|
|
|
|
return ApiResponse::NO_CONTENT->response();
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al eliminar SIM card',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/* ---------------------Importar Excel--------------------------- */
|
|
|
|
private $packageCache = [];
|
|
private $columnMap = [];
|
|
private $stats = [
|
|
'created' => 0,
|
|
'updated' => 0,
|
|
'assigned' => 0,
|
|
'packages_created' => 0,
|
|
'clients_created' => 0,
|
|
'sales_created' => 0,
|
|
'errors' => [],
|
|
];
|
|
|
|
public function import(Request $request)
|
|
{
|
|
$request->validate([
|
|
'file' => 'required|mimes:xlsx,xls,csv|max:10240',
|
|
]);
|
|
|
|
try {
|
|
$file = $request->file('file');
|
|
|
|
$spreadsheet = IOFactory::load($file->getRealPath());
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
$rows = $sheet->toArray();
|
|
|
|
// Leer encabezados de la primera fila
|
|
$this->buildColumnMap($rows[0]);
|
|
|
|
foreach ($rows as $index => $row) {
|
|
if ($index === 0) continue; // Saltar encabezados
|
|
|
|
// Iniciar transacción por fila
|
|
DB::beginTransaction();
|
|
|
|
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'),
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
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,
|
|
'message' => 'Error en la importación',
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
private function processRow(array $row, int $rowNumber = 0)
|
|
{
|
|
// Validar campos requeridos
|
|
if (empty($row['iccid']) || empty($row['msisdn'])) {
|
|
return;
|
|
}
|
|
|
|
// Buscar o crear la SIM
|
|
$sim = SimCard::where('iccid', $row['iccid'])->first();
|
|
|
|
if (!$sim) {
|
|
// No existe, crearla
|
|
$sim = SimCard::create([
|
|
'iccid' => $row['iccid'],
|
|
'msisdn' => $row['msisdn'],
|
|
'status' => SimCardStatus::AVAILABLE,
|
|
]);
|
|
|
|
$this->stats['created']++;
|
|
} else {
|
|
// Ya existe, actualizar (msisdn en caso de cambio)
|
|
$sim->update([
|
|
'msisdn' => $row['msisdn'],
|
|
]);
|
|
|
|
if (!isset($this->stats['updated'])) {
|
|
$this->stats['updated'] = 0;
|
|
}
|
|
$this->stats['updated']++;
|
|
}
|
|
|
|
// Determinar si es una venta (tiene usuario Y paquete)
|
|
$hasUsuario = !empty($row['usuario']) &&
|
|
strtolower(trim($row['usuario'])) !== 'si' &&
|
|
strtolower(trim($row['usuario'])) !== 'no';
|
|
$hasPaquete = !empty($row['paquetes']);
|
|
|
|
if ($hasUsuario && $hasPaquete) {
|
|
// Es una venta - procesar como venta completa
|
|
$this->processSale($sim, $row);
|
|
} else {
|
|
// No es venta - solo asignar paquete y/o cliente si existen
|
|
if ($hasPaquete) {
|
|
$this->processPackageFromText($sim, $row);
|
|
}
|
|
if ($hasUsuario) {
|
|
$this->assignToClient($sim, $row);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function processSale(SimCard $sim, array $row)
|
|
{
|
|
// Parsear el paquete desde el texto
|
|
$estadoSim = trim($row['paquetes'] ?? '');
|
|
$packageInfo = $this->parsePackageText($estadoSim);
|
|
|
|
if (!$packageInfo) {
|
|
$this->stats['errors'][] = [
|
|
'iccid' => $sim->iccid,
|
|
'estado_sim' => $estadoSim,
|
|
'reason' => 'No se pudo parsear el paquete para la venta'
|
|
];
|
|
return;
|
|
}
|
|
|
|
// Obtener o crear el paquete
|
|
$package = $this->getOrCreatePackage(
|
|
$packageInfo['type'],
|
|
$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()
|
|
?? Client::where('full_name', 'LIKE', "%{$usuario}%")->first()
|
|
?? Client::where(function ($query) use ($usuario) {
|
|
$query->whereRaw("CONCAT(name, ' ', IFNULL(paternal,''), ' ', IFNULL(maternal,'')) LIKE ?", ["%{$usuario}%"]);
|
|
})->first();
|
|
|
|
if (!$client) {
|
|
$nameParts = $this->splitFullName($usuario);
|
|
|
|
$client = Client::create([
|
|
'full_name' => $usuario,
|
|
'name' => $nameParts['name'],
|
|
'paternal' => $nameParts['paternal'],
|
|
'maternal' => $nameParts['maternal'],
|
|
]);
|
|
|
|
$this->stats['clients_created']++;
|
|
}
|
|
|
|
// Parsear fecha de venta
|
|
$saleDate = $this->parseSaleDate($row['fecha_venta'] ?? null);
|
|
|
|
// Validar y normalizar método de pago
|
|
$paymentMethod = $this->normalizePaymentMethod($row['metodo_pago'] ?? null);
|
|
|
|
// Crear la venta
|
|
$sale = Sale::create([
|
|
'client_id' => $client->id,
|
|
'cash_close_id' => null,
|
|
'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,
|
|
]);
|
|
|
|
// Asignar SIM al cliente
|
|
$existingRelation = ClientSim::where('client_id', $client->id)
|
|
->where('sim_card_id', $sim->id)
|
|
->where('is_active', true)
|
|
->exists();
|
|
|
|
if (!$existingRelation) {
|
|
ClientSim::create([
|
|
'client_id' => $client->id,
|
|
'sim_card_id' => $sim->id,
|
|
'assigned_at' => $saleDate,
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
// Asignar paquete a la SIM
|
|
$hasActivePackage = $sim->packages()
|
|
->wherePivot('package_id', $package->id)
|
|
->wherePivot('is_active', true)
|
|
->exists();
|
|
|
|
if (!$hasActivePackage) {
|
|
$sim->packages()->attach($package->id, [
|
|
'activated_at' => $saleDate,
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
// 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()->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') . ' 00:00:00';
|
|
} catch (\Exception $e) {
|
|
return now()->startOfDay()->format('Y-m-d H:i:s');
|
|
}
|
|
}
|
|
|
|
// Si es string, intentar parsear como DD/MM/YYYY
|
|
try {
|
|
// 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) {
|
|
// 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');
|
|
}
|
|
}
|
|
}
|
|
|
|
private function normalizePaymentMethod($method)
|
|
{
|
|
if (empty($method)) {
|
|
return 'import';
|
|
}
|
|
|
|
$method = strtolower(trim($method));
|
|
|
|
// Mapear variaciones comunes
|
|
$methodMap = [
|
|
'cash' => 'cash',
|
|
'efectivo' => 'cash',
|
|
'cash' => 'cash',
|
|
'card' => 'card',
|
|
'tarjeta' => 'card',
|
|
'credito' => 'card',
|
|
'debito' => 'card',
|
|
'transfer' => 'transfer',
|
|
'transferencia' => 'transfer',
|
|
'import' => 'import',
|
|
'importacion' => 'import',
|
|
];
|
|
|
|
return $methodMap[$method] ?? 'import';
|
|
}
|
|
|
|
private function processPackageFromText(SimCard $sim, array $row)
|
|
{
|
|
$estadoSim = trim($row['paquetes'] ?? '');
|
|
|
|
if (empty($estadoSim)) {
|
|
return;
|
|
}
|
|
|
|
$packageInfo = $this->parsePackageText($estadoSim);
|
|
|
|
if (!$packageInfo) {
|
|
$this->stats['errors'][] = [
|
|
'iccid' => $sim->iccid,
|
|
'estado_sim' => $estadoSim,
|
|
'reason' => 'No se pudo parsear el paquete'
|
|
];
|
|
return;
|
|
}
|
|
|
|
$package = $this->getOrCreatePackage(
|
|
$packageInfo['type'],
|
|
$packageInfo['price']
|
|
);
|
|
|
|
$hasActivePackage = $sim->packages()
|
|
->wherePivot('package_id', $package->id)
|
|
->wherePivot('is_active', true)
|
|
->exists();
|
|
|
|
if (!$hasActivePackage) {
|
|
$sim->packages()->attach($package->id, [
|
|
'activated_at' => now(),
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function parsePackageText(string $text): ?array
|
|
{
|
|
$text = strtolower($text);
|
|
|
|
$type = null;
|
|
if (str_contains($text, 'precarga') || str_contains($text, 'pre carga')) {
|
|
$type = 'Precarga';
|
|
} elseif (str_contains($text, 'prepago') || str_contains($text, 'pre pago')) {
|
|
$type = 'Prepago';
|
|
}
|
|
|
|
if (!$type) {
|
|
return null;
|
|
}
|
|
|
|
preg_match('/\$?\s*(\d+)(?:\.\d+)?/', $text, $matches);
|
|
$price = isset($matches[1]) ? (float) $matches[1] : 0;
|
|
|
|
return ['type' => $type, 'price' => $price];
|
|
}
|
|
|
|
private function getOrCreatePackage(string $type, float $price): Packages
|
|
{
|
|
$cacheKey = "{$type}_{$price}";
|
|
|
|
if (isset($this->packageCache[$cacheKey])) {
|
|
return $this->packageCache[$cacheKey];
|
|
}
|
|
|
|
$package = Packages::firstOrCreate(
|
|
[
|
|
'name' => $type,
|
|
'price' => (float) $price
|
|
],
|
|
[
|
|
'period' => 0,
|
|
'data_limit' => 0,
|
|
]
|
|
);
|
|
|
|
if ($package->wasRecentlyCreated) {
|
|
$this->stats['packages_created']++;
|
|
}
|
|
|
|
$this->packageCache[$cacheKey] = $package;
|
|
|
|
return $package;
|
|
}
|
|
|
|
private function assignToClient(SimCard $sim, array $row)
|
|
{
|
|
$usuario = trim($row['usuario'] ?? '');
|
|
|
|
if (empty($usuario) || strtolower($usuario) === 'si' || strtolower($usuario) === 'no') {
|
|
return;
|
|
}
|
|
|
|
$client = Client::where('full_name', $usuario)->first()
|
|
?? Client::where('full_name', 'LIKE', "%{$usuario}%")->first()
|
|
?? Client::where(function ($query) use ($usuario) {
|
|
$query->whereRaw("CONCAT(name, ' ', IFNULL(paternal,''), ' ', IFNULL(maternal,'')) LIKE ?", ["%{$usuario}%"]);
|
|
})->first();
|
|
|
|
if (!$client) {
|
|
$nameParts = $this->splitFullName($usuario);
|
|
|
|
$client = Client::create([
|
|
'full_name' => $usuario,
|
|
'name' => $nameParts['name'],
|
|
'paternal' => $nameParts['paternal'],
|
|
'maternal' => $nameParts['maternal'],
|
|
]);
|
|
|
|
$this->stats['clients_created']++;
|
|
}
|
|
|
|
$existingRelation = ClientSim::where('client_id', $client->id)
|
|
->where('sim_card_id', $sim->id)
|
|
->where('is_active', true)
|
|
->exists();
|
|
|
|
if ($existingRelation) {
|
|
return;
|
|
}
|
|
|
|
ClientSim::create([
|
|
'client_id' => $client->id,
|
|
'sim_card_id' => $sim->id,
|
|
'assigned_at' => now(),
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$sim->update(['status' => SimCardStatus::ASSIGNED]);
|
|
|
|
$this->stats['assigned']++;
|
|
}
|
|
|
|
private function splitFullName(string $fullName): array
|
|
{
|
|
$parts = array_filter(explode(' ', trim($fullName)));
|
|
$parts = array_values($parts);
|
|
|
|
return [
|
|
'name' => $parts[0] ?? '',
|
|
'paternal' => $parts[1] ?? '',
|
|
'maternal' => $parts[2] ?? '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Construye un mapa de nombres de columnas a índices
|
|
*/
|
|
private function buildColumnMap(array $headers)
|
|
{
|
|
$this->columnMap = [];
|
|
|
|
foreach ($headers as $index => $header) {
|
|
// Normalizar el nombre de la columna (mayúsculas, sin espacios extra)
|
|
$normalizedHeader = strtoupper(trim($header ?? ''));
|
|
|
|
if (!empty($normalizedHeader)) {
|
|
$this->columnMap[$normalizedHeader] = $index;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene el valor de una columna por su nombre
|
|
*/
|
|
private function getColumnValue(array $row, string $columnName)
|
|
{
|
|
$normalizedName = strtoupper(trim($columnName));
|
|
|
|
// Buscar en el mapa de columnas
|
|
if (isset($this->columnMap[$normalizedName])) {
|
|
$index = $this->columnMap[$normalizedName];
|
|
return $row[$index] ?? null;
|
|
}
|
|
|
|
// Si no se encuentra la columna, retornar null
|
|
return null;
|
|
}
|
|
}
|