Modificaciones a usuarios y roles #2
121
app/Http/Controllers/Netbien/CashCloseController.php
Normal file
121
app/Http/Controllers/Netbien/CashCloseController.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Netbien;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CashClose;
|
||||
use App\Models\Sale;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class CashCloseController extends Controller
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = CashClose::with('user:id,name')->withCount('sales');
|
||||
|
||||
if ($request->has('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('date')) {
|
||||
$query->whereDate('close_date', $request->date);
|
||||
}
|
||||
|
||||
$cashCloses = $query->orderBy('id', 'asc')
|
||||
->paginate(config('app.pagination'));
|
||||
|
||||
return ApiResponse::OK->response([
|
||||
'cash_closes' => $cashCloses,
|
||||
]);
|
||||
}
|
||||
|
||||
public function CloseCashClose($id)
|
||||
{
|
||||
$today = now()->format('Y-m-d');
|
||||
|
||||
$cashClose = CashClose::findOrFail($id);
|
||||
|
||||
$totalSales = Sale::where('cash_close_id', $id)->sum('total_amount');
|
||||
$paymentMethods = Sale::where('cash_close_id', $id)
|
||||
->select('payment_method', DB::raw('SUM(total_amount) as total'))
|
||||
->groupBy('payment_method')
|
||||
->get();
|
||||
|
||||
$cashClose->update([
|
||||
'income' => $totalSales,
|
||||
'balance' => $totalSales - $cashClose->exit,
|
||||
'payment_methods' => $paymentMethods,
|
||||
'status' => 'closed',
|
||||
'close_date' => $today,
|
||||
]);
|
||||
|
||||
return ApiResponse::OK->response([
|
||||
'message' => 'Corte de caja cerrado exitosamente.',
|
||||
'data' => $cashClose,
|
||||
]);
|
||||
}
|
||||
|
||||
public function report($id)
|
||||
{
|
||||
// Información del corte de caja
|
||||
$cashClose = CashClose::with('user:id,name')
|
||||
->withCount('sales')
|
||||
->findOrFail($id);
|
||||
|
||||
// Estadísticas por paquete (Total de Paquetes Vendidos por Tipo)
|
||||
$packageStats = DB::table('sale_items')
|
||||
->join('sales', 'sale_items.sale_id', '=', 'sales.id')
|
||||
->join('packages', 'sale_items.package_id', '=', 'packages.id')
|
||||
->where('sales.cash_close_id', $id)
|
||||
->select(
|
||||
'packages.name as paquete',
|
||||
DB::raw('COUNT(*) as total_vendidos')
|
||||
)
|
||||
->groupBy('packages.id', 'packages.name')
|
||||
->get();
|
||||
|
||||
// Estadísticas por duración (Total de Ventas por Duración)
|
||||
$durationStats = DB::table('sale_items')
|
||||
->join('sales', 'sale_items.sale_id', '=', 'sales.id')
|
||||
->join('packages', 'sale_items.package_id', '=', 'packages.id')
|
||||
->where('sales.cash_close_id', $id)
|
||||
->select(
|
||||
'packages.period as duracion_dias',
|
||||
DB::raw('COUNT(DISTINCT sales.id) as total_ventas')
|
||||
)
|
||||
->groupBy('packages.period')
|
||||
->orderBy('packages.period', 'asc')
|
||||
->get();
|
||||
|
||||
// Reporte detallado de ventas
|
||||
$detailedSales = DB::table('sales')
|
||||
->join('clients', 'sales.client_id', '=', 'clients.id')
|
||||
->join('sale_items', 'sales.id', '=', 'sale_items.sale_id')
|
||||
->join('packages', 'sale_items.package_id', '=', 'packages.id')
|
||||
->join('sim_cards', 'sale_items.sim_card_id', '=', 'sim_cards.id')
|
||||
->where('sales.cash_close_id', $id)
|
||||
->select(
|
||||
DB::raw("CONCAT(clients.name, ' ', clients.paternal, ' ', clients.maternal) as nombre_comprador"),
|
||||
'sim_cards.iccid as id_sim',
|
||||
'sim_cards.msisdn as numero_asignado',
|
||||
'packages.name as paquete',
|
||||
'packages.price as costo',
|
||||
'sales.payment_method as medio_pago'
|
||||
)
|
||||
->orderBy('sales.id', 'desc')
|
||||
->get();
|
||||
|
||||
return ApiResponse::OK->response([
|
||||
'cash_close' => $cashClose,
|
||||
'ventas por paquete' => $packageStats,
|
||||
'ventas por duracion' => $durationStats,
|
||||
'ventas detalladas' => $detailedSales,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@
|
||||
use App\Enums\SimCardStatus;
|
||||
use App\Http\Requests\Netbien\SaleStoreRequest;
|
||||
use App\Http\Requests\Netbien\SaleUpdateRequest;
|
||||
use App\Services\CashCloseService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||
|
||||
@ -66,8 +67,11 @@ public function store(SaleStoreRequest $request)
|
||||
$total += $package->price;
|
||||
}
|
||||
|
||||
$cashClose = CashCloseService::getOrCreateOpenCashClose();
|
||||
|
||||
$sale = Sale::create([
|
||||
'client_id' => $client->id,
|
||||
'cash_close_id' => $cashClose->id,
|
||||
'total_amount' => $total,
|
||||
'payment_method' => $request->payment_method,
|
||||
'sale_date' => now(),
|
||||
|
||||
@ -32,6 +32,7 @@ public function rules(): array
|
||||
'maternal' => ['required', 'string'],
|
||||
'email' => ['nullable', 'email'],
|
||||
'phone' => ['nullable', 'string', 'max:10'],
|
||||
'rfc' => ['required', 'string', 'max:13'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ public function rules(): array
|
||||
'maternal' => ['sometimes', 'string', 'max:100'],
|
||||
'email' => ['sometimes', 'email'],
|
||||
'phone' => ['nullable', 'string', 'max:20'],
|
||||
'rfc' => ['sometimes', 'string', 'max:13'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
85
app/Models/CashClose.php
Normal file
85
app/Models/CashClose.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class CashClose extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'balance',
|
||||
'income',
|
||||
'exit',
|
||||
'expected_balance',
|
||||
'difference',
|
||||
'close_date',
|
||||
'notes',
|
||||
'status',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'balance' => 'decimal:2',
|
||||
'income' => 'decimal:2',
|
||||
'exit' => 'decimal:2',
|
||||
'expected_balance' => 'decimal:2',
|
||||
'difference' => 'decimal:2',
|
||||
'close_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Relación con el usuario que realizó el corte
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con las ventas asociadas a este corte
|
||||
*/
|
||||
public function sales()
|
||||
{
|
||||
return $this->hasMany(Sale::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope para cortes abiertos
|
||||
*/
|
||||
public function scopeOpen($query)
|
||||
{
|
||||
return $query->where('status', 'open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope para cortes cerrados
|
||||
*/
|
||||
public function scopeClosed($query)
|
||||
{
|
||||
return $query->where('status', 'closed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope para cortes de una fecha específica
|
||||
*/
|
||||
public function scopeByDate($query, $date)
|
||||
{
|
||||
return $query->whereDate('close_date', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula automáticamente el balance
|
||||
*/
|
||||
public function calculateBalance()
|
||||
{
|
||||
$this->balance = $this->income - $this->exit;
|
||||
|
||||
if ($this->expected_balance) {
|
||||
$this->difference = $this->balance - $this->expected_balance;
|
||||
}
|
||||
|
||||
return $this->balance;
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ class Client extends Model
|
||||
'maternal',
|
||||
'email',
|
||||
'phone',
|
||||
'rfc',
|
||||
];
|
||||
|
||||
public function sales()
|
||||
|
||||
@ -17,6 +17,7 @@ class Sale extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'client_id',
|
||||
'cash_close_id',
|
||||
'total_amount',
|
||||
'payment_method',
|
||||
'sale_date',
|
||||
@ -31,4 +32,9 @@ public function saleItems()
|
||||
{
|
||||
return $this->hasMany(SaleItem::class);
|
||||
}
|
||||
|
||||
public function cashClose()
|
||||
{
|
||||
return $this->belongsTo(CashClose::class);
|
||||
}
|
||||
}
|
||||
|
||||
31
app/Services/CashCloseService.php
Normal file
31
app/Services/CashCloseService.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\CashClose;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CashCloseService
|
||||
{
|
||||
/**
|
||||
* Obtiene el corte de caja abierto del día o crea uno nuevo
|
||||
*/
|
||||
public static function getOrCreateOpenCashClose()
|
||||
{
|
||||
$today = now()->format('Y-m-d');
|
||||
$cashClose = CashClose::open()->byDate($today)->first();
|
||||
|
||||
if (!$cashClose) {
|
||||
$cashClose = CashClose::create([
|
||||
'close_date' => $today,
|
||||
'income' => 0,
|
||||
'exit' => 0,
|
||||
'balance' => 0,
|
||||
'status' => 'open',
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $cashClose;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?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('cash_closes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null');
|
||||
$table->decimal('balance', 10, 2);
|
||||
$table->decimal('income', 10, 2);
|
||||
$table->decimal('exit', 10, 2)->default(0);
|
||||
$table->enum('status', ['open', 'closed', 'reviewed'])->default('open')->comment('Estado del corte');
|
||||
$table->timestamp('close_date');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cash_closes');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
<?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('sales', function (Blueprint $table) {
|
||||
$table->foreignId('cash_close_id')->nullable()->after('client_id')->constrained('cash_closes')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('sales', function (Blueprint $table) {
|
||||
$table->dropForeign(['cash_close_id']);
|
||||
$table->dropColumn('cash_close_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
<?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('clients', function (Blueprint $table) {
|
||||
$table->string('rfc')->nullable()->after('phone');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('clients', function (Blueprint $table) {
|
||||
$table->dropColumn('rfc');
|
||||
});
|
||||
}
|
||||
};
|
||||
58
database/seeders/ClientSeeder.php
Normal file
58
database/seeders/ClientSeeder.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php namespace Database\Seeders;
|
||||
|
||||
use App\Models\Client;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ClientSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Ejecutar sembrado de base de datos
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$clients = [
|
||||
[
|
||||
'name' => 'Juan',
|
||||
'paternal' => 'Pérez',
|
||||
'maternal' => 'Gómez',
|
||||
'email' => 'juan.perez@example.com',
|
||||
'phone' => '551234567890',
|
||||
'rfc' => 'JUAP890123XXX',
|
||||
],
|
||||
[
|
||||
'name' => 'María',
|
||||
'paternal' => 'López',
|
||||
'maternal' => 'Hernández',
|
||||
'email' => 'maria.lopez@example.com',
|
||||
'phone' => '551234567891',
|
||||
'rfc' => 'MALO910203XXX',
|
||||
],
|
||||
[
|
||||
'name' => 'María',
|
||||
'paternal' => 'Hernández',
|
||||
'maternal' => 'Cruz',
|
||||
'email' => 'maria.hernandez@example.com',
|
||||
'phone' => '555-1003',
|
||||
],
|
||||
[
|
||||
'name' => 'Carlos',
|
||||
'paternal' => 'Sánchez',
|
||||
'maternal' => 'Ruiz',
|
||||
'email' => 'carlos.sanchez@example.com',
|
||||
'phone' => '555-1004',
|
||||
],
|
||||
[
|
||||
'name' => 'Laura',
|
||||
'paternal' => 'Gómez',
|
||||
'maternal' => 'Flores',
|
||||
'email' => 'laura.gomez@example.com',
|
||||
'phone' => '555-1005',
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($clients as $client) {
|
||||
Client::create($client);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
database/seeders/PackageSeeder.php
Normal file
25
database/seeders/PackageSeeder.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php namespace Database\Seeders;
|
||||
/**
|
||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class PackageSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Ejecutar sembrado de base de datos
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,42 @@
|
||||
<?php namespace Database\Seeders;
|
||||
/**
|
||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||
*/
|
||||
<?php
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Packages;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class Packages extends Seeder
|
||||
class PackagesSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Ejecutar sembrado de base de datos
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
$packages = [
|
||||
[
|
||||
'name' => 'Paquete 1',
|
||||
'price' => 100.00,
|
||||
'period' => 15,
|
||||
'data_limit' => 5,
|
||||
],
|
||||
[
|
||||
'name' => 'Paquete 2',
|
||||
'price' => 150.00,
|
||||
'period' => 20,
|
||||
'data_limit' => 10,
|
||||
],
|
||||
[
|
||||
'name' => 'Paquete 3',
|
||||
'price' => 200.00,
|
||||
'period' => 25,
|
||||
'data_limit' => 15,
|
||||
],
|
||||
[
|
||||
'name' => 'Paquete Premium',
|
||||
'price' => 250.00,
|
||||
'period' => 30,
|
||||
'data_limit' => 20,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($packages as $package) {
|
||||
Packages::create($package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,14 +3,15 @@
|
||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||
*/
|
||||
|
||||
use App\Models\SimCard;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
*
|
||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||
*
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class SimCardSeeder extends Seeder
|
||||
@ -20,6 +21,25 @@ class SimCardSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
$availables=[
|
||||
['iccid'=>'8986002212345678901','msisdn'=>'551234567890','status'=>'available'],
|
||||
['iccid'=>'8986002212345678902','msisdn'=>'551234567891','status'=>'available'],
|
||||
['iccid'=>'8986002212345678903','msisdn'=>'551234567892','status'=>'available'],
|
||||
['iccid'=>'8986002212345678904','msisdn'=>'551234567893','status'=>'available'],
|
||||
['iccid'=>'8986002212345678905','msisdn'=>'551234567894','status'=>'available'],
|
||||
['iccid'=>'8986002212345678906','msisdn'=>'551234567895','status'=>'available'],
|
||||
['iccid'=>'8986002212345678907','msisdn'=>'551234567896','status'=>'available'],
|
||||
['iccid'=>'8986002212345678908','msisdn'=>'551234567897','status'=>'available'],
|
||||
['iccid'=>'8986002212345678909','msisdn'=>'551234567898','status'=>'available'],
|
||||
['iccid'=>'8986002212345678910','msisdn'=>'551234567899','status'=>'available'],
|
||||
];
|
||||
|
||||
foreach ($availables as $simcard) {
|
||||
SimCard::create([
|
||||
'iccid' => $simcard['iccid'],
|
||||
'msisdn' => $simcard['msisdn'],
|
||||
'status' => $simcard['status'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ services:
|
||||
dockerfile: dockerfile
|
||||
working_dir: /var/www/netbien
|
||||
environment:
|
||||
- DB_HOST=mysql
|
||||
- DB_HOST=${DB_HOST}
|
||||
- DB_USERNAME=${DB_USERNAME}
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- DB_DATABASE=${DB_DATABASE}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Netbien\CashCloseController;
|
||||
use App\Http\Controllers\Netbien\PackagesController;
|
||||
use App\Http\Controllers\Netbien\SimCardController;
|
||||
use App\Http\Controllers\Netbien\ClientController;
|
||||
@ -30,6 +31,10 @@
|
||||
Route::resource('clients', ClientController::class);
|
||||
|
||||
Route::resource('sales', SaleController::class);
|
||||
|
||||
Route::get('cash-closes', [CashCloseController::class, 'index']);
|
||||
Route::put('cash-closes/{id}/close', [CashCloseController::class, 'CloseCashClose']);
|
||||
Route::get('cash-closes/{id}/report', [CashCloseController::class, 'report']);
|
||||
});
|
||||
|
||||
/** Rutas públicas */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user