ADD: Clientes y ventas

This commit is contained in:
Juan Felipe Zapata Moreno 2025-11-04 23:29:34 -06:00
parent 87648a5318
commit abfa2fe1fd
26 changed files with 860 additions and 22 deletions

View File

@ -0,0 +1,59 @@
<?php namespace App\Http\Controllers\Netbien;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Client;
use App\Http\Requests\Netbien\ClientStoreRequest;
use App\Http\Requests\Netbien\ClientUpdateRequest;
/**
*
*/
class ClientController extends Controller
{
public function index()
{
$clients = Client::with('simCards:id,msisdn')->orderBy('id', 'asc')->paginate(config('app.pagination'));
return ApiResponse::OK->response([
'data' => $clients,
]);
}
public function store(ClientStoreRequest $request)
{
$client = Client::create($request->validated());
return ApiResponse::CREATED->response([
'data' => $client,
]);
}
public function update(ClientUpdateRequest $request, Client $client)
{
$client->update($request->validated());
return ApiResponse::OK->response([
'data' => $client,
]);
}
public function destroy(Client $client)
{
$hasActiveSims = $client->simCards()
->wherePivot('is_active', true)
->exists();
if ($hasActiveSims) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'No se puede eliminar el cliente porque tiene SIMs activas',
]);
}
$client->delete();
return ApiResponse::NO_CONTENT->response();
}
}

View File

@ -5,6 +5,7 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Netbien\PackagesStoreRequest;
use App\Http\Requests\Netbien\PackagesUpdateRequest;
use App\Models\Packages;
use Notsoweb\ApiResponse\Enums\ApiResponse;
@ -33,14 +34,19 @@ public function store(PackagesStoreRequest $request)
]);
}
public function update(PackagesStoreRequest $request, Packages $package)
public function update(PackagesUpdateRequest $request, Packages $package)
{
$validated = $request->validated();
$package->update($validated);
$package->update($request->validated());
return ApiResponse::OK->response([
'data' => $package,
]);
}
public function destroy(Packages $package)
{
$package->delete();
return ApiResponse::NO_CONTENT->response();
}
}

View File

@ -0,0 +1,187 @@
<?php namespace App\Http\Controllers\Netbien;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Sale;
use App\Models\SaleItem;
use App\Models\Client;
use App\Models\Packages;
use App\Models\SimCard;
use App\Models\ClientSim;
use App\Enums\SimCardStatus;
use App\Http\Requests\Netbien\SaleStoreRequest;
use App\Http\Requests\Netbien\SaleUpdateRequest;
use Illuminate\Support\Facades\DB;
use Notsoweb\ApiResponse\Enums\ApiResponse;
/**
*/
class SaleController extends Controller
{
public function index(Request $request)
{
$query = Sale::with([
'client:id,name,paternal,maternal,email',
'saleItems.simCard:id,iccid,msisdn',
'saleItems.package:id,name,price'
]);
// Filtro por fecha
if ($request->has('date')) {
$query->whereDate('sale_date', $request->date);
}
// Filtro por cliente
if ($request->has('client_id')) {
$query->where('client_id', $request->client_id);
}
// Filtro por método de pago
if ($request->has('payment_method')) {
$query->where('payment_method', $request->payment_method);
}
$sales = $query->orderBy('id', 'asc')
->paginate(config('app.pagination'));
return ApiResponse::OK->response([
'Sale' => $sales,
]);
}
public function store(SaleStoreRequest $request)
{
try {
DB::beginTransaction();
if ($request->has('client_id')) {
$client = Client::findOrFail($request->client_id);
} else {
$client = Client::create($request->client);
}
$total = 0;
foreach ($request->saleItems as $item) {
$package = Packages::findOrFail($item['package_id']);
$total += $package->price;
}
$sale = Sale::create([
'client_id' => $client->id,
'total_amount' => $total,
'payment_method' => $request->payment_method,
'sale_date' => now(),
]);
foreach ($request->saleItems as $item) {
$sim = SimCard::findOrFail($item['sim_card_id']);
$package = Packages::findOrFail($item['package_id']);
if ($sim->status !== SimCardStatus::AVAILABLE) {
throw new \Exception("La SIM {$sim->msisdn} no está disponible");
}
SaleItem::create([
'sale_id' => $sale->id,
'sim_card_id' => $sim->id,
'package_id' => $package->id,
]);
ClientSim::create([
'client_id' => $client->id,
'sim_card_id' => $sim->id,
'assigned_at' => now(),
'is_active' => true,
]);
$sim->packages()->attach($package->id, [
'activated_at' => now(),
'is_active' => true,
]);
$sim->update(['status' => SimCardStatus::ASSIGNED]);
}
DB::commit();
$sale->load([
'client',
'saleItems.simCard',
'saleItems.package'
]);
return ApiResponse::CREATED->response([
'Sale' => $sale,
'message' => 'Venta registrada exitosamente',
]);
} catch (\Exception $e) {
DB::rollBack();
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al registrar la venta',
'error' => $e->getMessage(),
]);
}
}
public function update(SaleUpdateRequest $request, Sale $sale)
{
$sale->update($request->only(['payment_method']));
return ApiResponse::OK->response([
'data' => $sale,
'message' => 'Venta actualizada exitosamente',
]);
}
public function destroy(Sale $sale)
{
try {
DB::beginTransaction();
// Obtener todos los items de la venta
$items = $sale->items;
foreach ($items as $item) {
$sim = $item->simCard;
// Desactivar el paquete de la SIM
$sim->packages()
->wherePivot('package_id', $item->package_id)
->wherePivot('is_active', true)
->update([
'is_active' => false,
'deactivated_at' => now()
]);
//Liberar la SIM del cliente
ClientSim::where('client_id', $sale->client_id)
->where('sim_card_id', $sim->id)
->where('is_active', true)
->update([
'is_active' => false,
'released_at' => now()
]);
//Cambiar status de la SIM a disponible
$sim->update(['status' => SimCardStatus::AVAILABLE]);
}
//Eliminar la venta (cascade)
$sale->delete();
DB::commit();
return ApiResponse::NO_CONTENT->response();
} catch (\Exception $e) {
DB::rollBack();
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al cancelar la venta',
'error' => $e->getMessage(),
]);
}
}
}

View File

@ -7,6 +7,7 @@
use App\Http\Requests\Netbien\SimCardStoreRequest;
use App\Http\Requests\Netbien\SimCardUpdateRequest;
use App\Models\SimCard;
use Illuminate\Support\Facades\DB;
use Notsoweb\ApiResponse\Enums\ApiResponse;
/**
@ -16,7 +17,7 @@ class SimCardController extends Controller
{
public function index()
{
$simCards = SimCard::orderBy('id', 'asc')->paginate(config('app.pagination'));
$simCards = SimCard::with('packSims.package:id,name')->orderBy('id', 'asc')->paginate(config('app.pagination'));
return ApiResponse::OK->response([
'data' => $simCards,
@ -25,11 +26,33 @@ public function index()
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)

View File

@ -0,0 +1,47 @@
<?php namespace App\Http\Requests\Netbien;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
/**
* Almacenar rol
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class ClientStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => ['required', 'string'],
'paternal' => ['required', 'string'],
'maternal' => ['required', 'string'],
'email' => ['nullable', 'email'],
'phone' => ['nullable', 'string', 'max:10'],
];
}
public function messages() : array
{
return [
'name.required' => 'El nombre es obligatorio.',
'paternal.required' => 'El apellido paterno es obligatorio.',
'maternal.required' => 'El apellido materno es obligatorio.',
'email.email' => 'El email debe ser válido.',
];
}
}

View File

@ -0,0 +1,47 @@
<?php namespace App\Http\Requests\Netbien;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
/**
* Almacenar rol
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class ClientUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => ['sometimes', 'string', 'max:255'],
'paternal' => ['sometimes', 'string', 'max:100'],
'maternal' => ['sometimes', 'string', 'max:100'],
'email' => ['sometimes', 'email'],
'phone' => ['nullable', 'string', 'max:20'],
];
}
public function messages() : array
{
return [
'name.required' => 'El campo Nombre es obligatorio.',
'email.required' => 'El campo Correo Electrónico es obligatorio.',
'email.email' => 'El campo Correo Electrónico debe ser una dirección de correo válida.',
'phone.required' => 'El campo Teléfono es obligatorio.',
];
}
}

View File

@ -26,9 +26,9 @@ public function rules(): array
{
return [
'name' => ['required', 'string', 'max:80'],
'price' => ['required', 'numeric', 'min:0'],
'period' => ['required', 'integer', 'min:1'],
'data_limit' => ['required', 'integer', 'min:0'],
'price' => ['required', 'float'],
'period' => ['required', 'float'],
'data_limit' => ['required', 'integer'],
];
}
@ -45,7 +45,6 @@ public function messages() : array
'period.required' => 'El campo Periodo es obligatorio.',
'data_limit.required' => 'El campo Límite de Datos es obligatorio.',
'data_limit.integer' => 'El campo Límite de Datos debe ser un número entero.',
'data_limit.min' => 'El campo Límite de Datos no debe ser negativo.',
];
}

View File

@ -25,10 +25,10 @@ public function authorize(): bool
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:80'],
'price' => ['required', 'numeric', 'min:0'],
'period' => ['required', 'string', 'max:20'],
'data_limit' => ['required', 'integer', 'min:0'],
'name' => ['sometimes', 'string', 'max:80'],
'price' => ['sometimes', 'numeric', 'min:0'],
'period' => ['sometimes', 'numeric', 'min:1'],
'data_limit' => ['sometimes', 'integer', 'min:0'],
];
}
@ -44,8 +44,6 @@ public function messages() : array
'price.min' => 'El campo Precio no debe ser negativo.',
'period.required' => 'El campo Periodo es obligatorio.',
'period.string' => 'El campo Periodo debe ser una cadena de texto.',
'period.max' => 'El campo Periodo no debe exceder los 20 caracteres.',
'data_limit.required' => 'El campo Límite de Datos es obligatorio.',
'data_limit.integer' => 'El campo Límite de Datos debe ser un número entero.',

View File

@ -0,0 +1,58 @@
<?php namespace App\Http\Requests\Netbien;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
/**
* Almacenar rol
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class SaleStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'client_id' => ['nullable', 'integer', 'exists:clients,id'],
'client' => ['required_without:client_id', 'array'],
'client.name' => ['required_with:client', 'string'],
'client.paternal' => ['required_with:client', 'string'],
'client.maternal' => ['required_with:client', 'string'],
'client.email' => ['required_with:client', 'email'],
'client.phone' => ['nullable', 'string', 'max:10'],
'payment_method' => ['required', 'string', 'in:cash,card,transfer'],
'saleItems' => ['required', 'array', 'min:1'],
'saleItems.*.sim_card_id' => ['required', 'integer', 'exists:sim_cards,id'],
'saleItems.*.package_id' => ['required', 'integer', 'exists:packages,id'],
];
}
public function messages(): array
{
return [
'client_id.exists' => 'El cliente seleccionado no existe.',
'client.required_without' => 'Debe proporcionar un cliente existente o crear uno nuevo.',
'payment_method.required' => 'El método de pago es obligatorio.',
'payment_method.in' => 'El método de pago debe ser: efectivo, tarjeta o transferencia.',
'saleItems.required' => 'Debe agregar al menos un item a la venta.',
'saleItems.*.sim_card_id.exists' => 'Una de las SIM seleccionadas no existe.',
'saleItems.*.package_id.exists' => 'Uno de los paquetes seleccionados no existe.',
];
}
}

View File

@ -0,0 +1,41 @@
<?php namespace App\Http\Requests\Netbien;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Request para actualizar una venta
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class SaleUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
*
* IMPORTANTE: Generalmente no se permite modificar los items de una venta
* ya registrada por temas de trazabilidad contable. Solo se permite
* actualizar campos administrativos como método de pago o notas.
*/
public function rules(): array
{
return [
'payment_method' => ['sometimes', 'string', 'in:cash,card,transfer'],
];
}
public function messages(): array
{
return [
'payment_method.in' => 'El método de pago debe ser: efectivo, tarjeta o transferencia.',
];
}
}

View File

@ -29,6 +29,7 @@ public function rules(): array
return [
'iccid' => ['required', 'string', 'max:25', 'unique:sim_cards,iccid'],
'msisdn' => ['required', 'string', 'max:10', 'unique:sim_cards,msisdn'],
'package_id' => ['nullable', 'integer', 'exists:packages,id'],
];
}
@ -44,6 +45,9 @@ public function messages() : array
'msisdn.string' => 'El campo MSISDN debe ser una cadena de texto.',
'msisdn.max' => 'El campo MSISDN no debe exceder los 10 caracteres.',
'msisdn.unique' => 'El MSISDN ya está en uso.',
'package_id.integer' => 'El paquete debe ser un número entero.',
'package_id.exists' => 'El paquete seleccionado no existe.',
];
}
}

View File

@ -35,7 +35,6 @@ public function messages() : array
{
return [
'msisdn.required' => 'El campo MSISDN es obligatorio.',
'msisdn.string' => 'El campo MSISDN debe ser una cadena de texto.',
'msisdn.max' => 'El campo MSISDN no debe exceder los 10 caracteres.',
'msisdn.unique' => 'El MSISDN ya está en uso.',
];

42
app/Models/Client.php Normal file
View File

@ -0,0 +1,42 @@
<?php namespace App\Models;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Database\Eloquent\Model;
/**
* Descripción
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class Client extends Model
{
protected $fillable = [
'name',
'paternal',
'maternal',
'email',
'phone',
];
public function sales()
{
return $this->hasMany(Sale::class);
}
public function clientSims()
{
return $this->hasMany(ClientSim::class);
}
public function simCards()
{
return $this->belongsToMany(SimCard::class, 'client_sims')
->withPivot('assigned_at', 'released_at', 'is_active')
->withTimestamps();
}
}

36
app/Models/ClientSim.php Normal file
View File

@ -0,0 +1,36 @@
<?php namespace App\Models;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Database\Eloquent\Model;
/**
* Descripción
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class ClientSim extends Model
{
protected $fillable = [
'client_id',
'sim_card_id',
'assigned_at',
'released_at',
'is_active',
];
public function client()
{
return $this->belongsTo(Client::class);
}
public function simCard()
{
return $this->belongsTo(SimCard::class);
}
}

View File

@ -18,11 +18,17 @@ class PackSim extends Model
protected $fillable = [
'package_id',
'sim_card_id',
'activated_at',
'deactivated_at',
'is_active',
];
protected $casts = [
'package_id' => 'integer',
'sim_card_id' => 'integer',
'activated_at' => 'datetime',
'deactivated_at' => 'datetime',
'is_active' => 'boolean',
];
public function package()

View File

@ -29,8 +29,34 @@ class Packages extends Model
'data_limit' => 'integer',
];
// Relación con la tabla pivote
public function packSims()
{
return $this->hasMany(PackSim::class, 'package_id');
}
// Relación muchos a muchos con SIM cards
public function simCards()
{
return $this->belongsToMany(
SimCard::class,
'pack_sims',
'package_id',
'sim_card_id'
)->withPivot('activated_at', 'deactivated_at', 'is_active')
->withTimestamps();
}
// SIM cards activas con este paquete
public function activeSimCards()
{
return $this->belongsToMany(
SimCard::class,
'pack_sims',
'package_id',
'sim_card_id'
)->wherePivot('is_active', true)
->withPivot('activated_at', 'deactivated_at', 'is_active')
->withTimestamps();
}
}

34
app/Models/Sale.php Normal file
View File

@ -0,0 +1,34 @@
<?php namespace App\Models;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Database\Eloquent\Model;
/**
* Descripción
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class Sale extends Model
{
protected $fillable = [
'client_id',
'total_amount',
'payment_method',
'sale_date',
];
public function client()
{
return $this->belongsTo(Client::class);
}
public function saleItems()
{
return $this->hasMany(SaleItem::class);
}
}

38
app/Models/SaleItem.php Normal file
View File

@ -0,0 +1,38 @@
<?php namespace App\Models;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Database\Eloquent\Model;
/**
* Descripción
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class SaleItem extends Model
{
protected $fillable = [
'sale_id',
'sim_card_id',
'package_id',
];
public function sale()
{
return $this->belongsTo(Sale::class);
}
public function simCard()
{
return $this->belongsTo(SimCard::class);
}
public function package()
{
return $this->belongsTo(Packages::class);
}
}

View File

@ -26,8 +26,35 @@ class SimCard extends Model
'status' => SimCardStatus::class,
];
// Relación con la tabla pivote
public function packSims()
{
return $this->hasMany(PackSim::class, 'sim_card_id');
}
// Relación muchos a muchos con paquetes
public function packages()
{
return $this->belongsToMany(
Packages::class,
'pack_sims',
'sim_card_id',
'package_id'
)->withPivot('activated_at', 'deactivated_at', 'is_active')
->withTimestamps();
}
// Paquete actualmente activo
public function activePackage()
{
return $this->belongsToMany(
Packages::class,
'pack_sims',
'sim_card_id',
'package_id'
)->wherePivot('is_active', true)
->withPivot('activated_at', 'deactivated_at', 'is_active')
->withTimestamps()
->limit(1);
}
}

View File

@ -14,9 +14,9 @@ public function up(): void
Schema::create('packages', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('price');
$table->float('price');
$table->integer('period');
$table->integer('data_limit');
$table->float('data_limit');
$table->timestamps();
});
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('pack_sims', function (Blueprint $table) {
$table->timestamp('activated_at')->nullable()->after('package_id');
$table->timestamp('deactivated_at')->nullable()->after('activated_at');
$table->boolean('is_active')->default(true)->after('deactivated_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('pack_sims', function (Blueprint $table) {
$table->dropColumn(['activated_at', 'deactivated_at', 'is_active']);
});
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('clients', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('paternal');
$table->string('maternal');
$table->string('email')->unique();
$table->string('phone')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('clients');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('client_sims', function (Blueprint $table) {
$table->id();
$table->foreignId('client_id')->constrained('clients')->onDelete('cascade');
$table->foreignId('sim_card_id')->constrained('sim_cards')->onDelete('cascade');
$table->date('assigned_at');
$table->date('released_at')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('client_sims');
}
};

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('sales', function (Blueprint $table) {
$table->id();
$table->foreignId('client_id')->constrained('clients')->onDelete('cascade');
$table->decimal('total_amount', 10, 2);
$table->string('payment_method');
$table->date('sale_date');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sales');
}
};

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('sale_items', function (Blueprint $table) {
$table->id();
$table->foreignId('sale_id')->constrained('sales')->onDelete('cascade');
$table->foreignId('sim_card_id')->constrained('sim_cards')->onDelete('cascade');
$table->foreignId('package_id')->constrained('packages')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sale_items');
}
};

View File

@ -2,6 +2,8 @@
use App\Http\Controllers\Netbien\PackagesController;
use App\Http\Controllers\Netbien\SimCardController;
use App\Http\Controllers\Netbien\ClientController;
use App\Http\Controllers\Netbien\SaleController;
use Illuminate\Support\Facades\Route;
/**
@ -24,6 +26,10 @@
Route::resource('sim-cards', SimCardController::class);
Route::resource('packages', PackagesController::class);
Route::resource('clients', ClientController::class);
Route::resource('sales', SaleController::class);
});
/** Rutas públicas */