ADD: Clientes y ventas
This commit is contained in:
parent
87648a5318
commit
abfa2fe1fd
59
app/Http/Controllers/Netbien/ClientController.php
Normal file
59
app/Http/Controllers/Netbien/ClientController.php
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Netbien\PackagesStoreRequest;
|
use App\Http\Requests\Netbien\PackagesStoreRequest;
|
||||||
|
use App\Http\Requests\Netbien\PackagesUpdateRequest;
|
||||||
use App\Models\Packages;
|
use App\Models\Packages;
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
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($request->validated());
|
||||||
|
|
||||||
$package->update($validated);
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
return ApiResponse::OK->response([
|
||||||
'data' => $package,
|
'data' => $package,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function destroy(Packages $package)
|
||||||
|
{
|
||||||
|
$package->delete();
|
||||||
|
|
||||||
|
return ApiResponse::NO_CONTENT->response();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
app/Http/Controllers/Netbien/SaleController.php
Normal file
187
app/Http/Controllers/Netbien/SaleController.php
Normal 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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@
|
|||||||
use App\Http\Requests\Netbien\SimCardStoreRequest;
|
use App\Http\Requests\Netbien\SimCardStoreRequest;
|
||||||
use App\Http\Requests\Netbien\SimCardUpdateRequest;
|
use App\Http\Requests\Netbien\SimCardUpdateRequest;
|
||||||
use App\Models\SimCard;
|
use App\Models\SimCard;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,7 +17,7 @@ class SimCardController extends Controller
|
|||||||
{
|
{
|
||||||
public function index()
|
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([
|
return ApiResponse::OK->response([
|
||||||
'data' => $simCards,
|
'data' => $simCards,
|
||||||
@ -25,11 +26,33 @@ public function index()
|
|||||||
|
|
||||||
public function store(SimCardStoreRequest $request)
|
public function store(SimCardStoreRequest $request)
|
||||||
{
|
{
|
||||||
$simCard = SimCard::create($request->validated());
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
return ApiResponse::CREATED->response([
|
$simCard = SimCard::create($request->validated());
|
||||||
'data' => $simCard,
|
|
||||||
]);
|
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)
|
public function update(SimCardUpdateRequest $request, SimCard $simCard)
|
||||||
|
|||||||
47
app/Http/Requests/Netbien/ClientStoreRequest.php
Normal file
47
app/Http/Requests/Netbien/ClientStoreRequest.php
Normal 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.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/Http/Requests/Netbien/ClientUpdateRequest.php
Normal file
47
app/Http/Requests/Netbien/ClientUpdateRequest.php
Normal 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.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,9 +26,9 @@ public function rules(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => ['required', 'string', 'max:80'],
|
'name' => ['required', 'string', 'max:80'],
|
||||||
'price' => ['required', 'numeric', 'min:0'],
|
'price' => ['required', 'float'],
|
||||||
'period' => ['required', 'integer', 'min:1'],
|
'period' => ['required', 'float'],
|
||||||
'data_limit' => ['required', 'integer', 'min:0'],
|
'data_limit' => ['required', 'integer'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,6 @@ public function messages() : array
|
|||||||
'period.required' => 'El campo Periodo es obligatorio.',
|
'period.required' => 'El campo Periodo es obligatorio.',
|
||||||
|
|
||||||
'data_limit.required' => 'El campo Límite de Datos 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.',
|
'data_limit.min' => 'El campo Límite de Datos no debe ser negativo.',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,10 +25,10 @@ public function authorize(): bool
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => ['required', 'string', 'max:80'],
|
'name' => ['sometimes', 'string', 'max:80'],
|
||||||
'price' => ['required', 'numeric', 'min:0'],
|
'price' => ['sometimes', 'numeric', 'min:0'],
|
||||||
'period' => ['required', 'string', 'max:20'],
|
'period' => ['sometimes', 'numeric', 'min:1'],
|
||||||
'data_limit' => ['required', 'integer', 'min:0'],
|
'data_limit' => ['sometimes', 'integer', 'min:0'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +44,6 @@ public function messages() : array
|
|||||||
'price.min' => 'El campo Precio no debe ser negativo.',
|
'price.min' => 'El campo Precio no debe ser negativo.',
|
||||||
|
|
||||||
'period.required' => 'El campo Periodo es obligatorio.',
|
'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.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.integer' => 'El campo Límite de Datos debe ser un número entero.',
|
||||||
|
|||||||
58
app/Http/Requests/Netbien/SaleStoreRequest.php
Normal file
58
app/Http/Requests/Netbien/SaleStoreRequest.php
Normal 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.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Http/Requests/Netbien/SaleUpdateRequest.php
Normal file
41
app/Http/Requests/Netbien/SaleUpdateRequest.php
Normal 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.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ public function rules(): array
|
|||||||
return [
|
return [
|
||||||
'iccid' => ['required', 'string', 'max:25', 'unique:sim_cards,iccid'],
|
'iccid' => ['required', 'string', 'max:25', 'unique:sim_cards,iccid'],
|
||||||
'msisdn' => ['required', 'string', 'max:10', 'unique:sim_cards,msisdn'],
|
'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.string' => 'El campo MSISDN debe ser una cadena de texto.',
|
||||||
'msisdn.max' => 'El campo MSISDN no debe exceder los 10 caracteres.',
|
'msisdn.max' => 'El campo MSISDN no debe exceder los 10 caracteres.',
|
||||||
'msisdn.unique' => 'El MSISDN ya está en uso.',
|
'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.',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,6 @@ public function messages() : array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'msisdn.required' => 'El campo MSISDN es obligatorio.',
|
'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.max' => 'El campo MSISDN no debe exceder los 10 caracteres.',
|
||||||
'msisdn.unique' => 'El MSISDN ya está en uso.',
|
'msisdn.unique' => 'El MSISDN ya está en uso.',
|
||||||
];
|
];
|
||||||
42
app/Models/Client.php
Normal file
42
app/Models/Client.php
Normal 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
36
app/Models/ClientSim.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,11 +18,17 @@ class PackSim extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'package_id',
|
'package_id',
|
||||||
'sim_card_id',
|
'sim_card_id',
|
||||||
|
'activated_at',
|
||||||
|
'deactivated_at',
|
||||||
|
'is_active',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'package_id' => 'integer',
|
'package_id' => 'integer',
|
||||||
'sim_card_id' => 'integer',
|
'sim_card_id' => 'integer',
|
||||||
|
'activated_at' => 'datetime',
|
||||||
|
'deactivated_at' => 'datetime',
|
||||||
|
'is_active' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function package()
|
public function package()
|
||||||
|
|||||||
@ -29,8 +29,34 @@ class Packages extends Model
|
|||||||
'data_limit' => 'integer',
|
'data_limit' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Relación con la tabla pivote
|
||||||
public function packSims()
|
public function packSims()
|
||||||
{
|
{
|
||||||
return $this->hasMany(PackSim::class, 'package_id');
|
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
34
app/Models/Sale.php
Normal 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
38
app/Models/SaleItem.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,8 +26,35 @@ class SimCard extends Model
|
|||||||
'status' => SimCardStatus::class,
|
'status' => SimCardStatus::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Relación con la tabla pivote
|
||||||
public function packSims()
|
public function packSims()
|
||||||
{
|
{
|
||||||
return $this->hasMany(PackSim::class, 'sim_card_id');
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ public function up(): void
|
|||||||
Schema::create('packages', function (Blueprint $table) {
|
Schema::create('packages', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->integer('price');
|
$table->float('price');
|
||||||
$table->integer('period');
|
$table->integer('period');
|
||||||
$table->integer('data_limit');
|
$table->float('data_limit');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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');
|
||||||
|
}
|
||||||
|
};
|
||||||
31
database/migrations/2025_11_04_221603_create_sales_table.php
Normal file
31
database/migrations/2025_11_04_221603_create_sales_table.php
Normal 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');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Netbien\PackagesController;
|
use App\Http\Controllers\Netbien\PackagesController;
|
||||||
use App\Http\Controllers\Netbien\SimCardController;
|
use App\Http\Controllers\Netbien\SimCardController;
|
||||||
|
use App\Http\Controllers\Netbien\ClientController;
|
||||||
|
use App\Http\Controllers\Netbien\SaleController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,6 +26,10 @@
|
|||||||
Route::resource('sim-cards', SimCardController::class);
|
Route::resource('sim-cards', SimCardController::class);
|
||||||
|
|
||||||
Route::resource('packages', PackagesController::class);
|
Route::resource('packages', PackagesController::class);
|
||||||
|
|
||||||
|
Route::resource('clients', ClientController::class);
|
||||||
|
|
||||||
|
Route::resource('sales', SaleController::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Rutas públicas */
|
/** Rutas públicas */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user