From 4d6059a1e90320144dd6dcc1e87cfebe09cd1e50 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Fri, 7 Nov 2025 17:33:39 -0600 Subject: [PATCH] Corte de caja actualizado --- .../Netbien/CashCloseController.php | 50 +++++++++++--- .../Controllers/Netbien/ClientController.php | 23 +++++++ app/Models/CashClose.php | 44 ++++--------- app/Services/CashCloseService.php | 12 ++-- ..._11_05_114844_create_cash_closes_table.php | 2 +- ...fy_clients_table_rfc_email_constraints.php | 24 +++++++ ...831_modify_cash_closes_table_structure.php | 55 ++++++++++++++++ database/seeders/ClientSeeder.php | 3 + database/seeders/DevSeeder.php | 8 ++- docker-compose.yml | 66 +++++++++---------- routes/api.php | 3 +- 11 files changed, 208 insertions(+), 82 deletions(-) create mode 100644 database/migrations/2025_11_07_111354_modify_clients_table_rfc_email_constraints.php create mode 100644 database/migrations/2025_11_07_163831_modify_cash_closes_table_structure.php diff --git a/app/Http/Controllers/Netbien/CashCloseController.php b/app/Http/Controllers/Netbien/CashCloseController.php index f656cc7..a899e57 100644 --- a/app/Http/Controllers/Netbien/CashCloseController.php +++ b/app/Http/Controllers/Netbien/CashCloseController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Models\CashClose; use App\Models\Sale; +use App\Models\SaleItem; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Notsoweb\ApiResponse\Enums\ApiResponse; @@ -35,28 +36,59 @@ public function index(Request $request) ]); } - public function closeCashClose($id) + public function closeCashClose(Request $request) { - $today = now()->format('Y-m-d'); + $request ->validate([ + 'exit' => 'sometimes|numeric|min:0', + ]); - $cashClose = CashClose::findOrFail($id); + $cashClose = CashClose::open()->first(); - $totalSales = Sale::where('cash_close_id', $id)->sum('total_amount'); - $paymentMethods = Sale::where('cash_close_id', $id) + if(!$cashClose) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No hay un corte de caja abierto para cerrar.', + ]); + } + + $totalSales = Sale::where('cash_close_id', $cashClose->id)->sum('total_amount'); + + $paymentMethods = Sale::where('cash_close_id', $cashClose->id) ->select('payment_method', DB::raw('SUM(total_amount) as total')) ->groupBy('payment_method') - ->get(); + ->pluck('total', 'payment_method'); + + $exit = $request->input('exit', 0); $cashClose->update([ + 'closed_at' => now(), 'income' => $totalSales, - 'balance' => $totalSales - $cashClose->exit, + 'exit' => $exit, + 'income_cash' => $paymentMethods->get('cash', 0), + 'income_card' => $paymentMethods->get('card', 0), + 'income_transfer' => $paymentMethods->get('transfer', 0), 'status' => 'closed', - 'close_date' => $today, ]); + $balanceFinal = $cashClose->initial_balance + $totalSales - $exit; + return ApiResponse::OK->response([ 'message' => 'Corte de caja cerrado exitosamente.', - 'data' => $cashClose, + 'cash_close' => $cashClose->fresh('user'), + 'resumen' => [ + 'periodo' => [ + 'apertura' => $cashClose->opened_at, + 'cierre' => $cashClose->closed_at, + ], + 'totales' => [ + 'fondo_inicial' => $cashClose->initial_balance, + 'total_ventas' => $totalSales, + 'efectivo' => $paymentMethods->get('cash', 0), + 'tarjeta' => $paymentMethods->get('card', 0), + 'transferencia' => $paymentMethods->get('transfer', 0), + 'egresos' => $exit, + 'balance_final' => $balanceFinal, + ], + ], ]); } diff --git a/app/Http/Controllers/Netbien/ClientController.php b/app/Http/Controllers/Netbien/ClientController.php index 76d4453..c85d687 100644 --- a/app/Http/Controllers/Netbien/ClientController.php +++ b/app/Http/Controllers/Netbien/ClientController.php @@ -21,6 +21,29 @@ public function index() ]); } + public function search(Request $request) + { + $request->validate([ + 'filter' => 'required|string|min:2', + ]); + + $search = $request->input('filter'); + + $clients = Client::with('simCards:id,msisdn') + ->where(function($q) use ($search) { + $q->whereRaw("CONCAT(name, ' ', paternal, ' ', maternal) LIKE ?", ["%{$search}%"]) + ->orWhere('rfc', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('phone', 'like', "%{$search}%"); + }) + ->orderBy('id', 'asc') + ->paginate(config('app.pagination')); + + return ApiResponse::OK->response([ + 'data' => $clients, + ]); + } + public function show(Client $client) { $client->load('simCards:id,msisdn'); diff --git a/app/Models/CashClose.php b/app/Models/CashClose.php index a75072a..3ca6cef 100644 --- a/app/Models/CashClose.php +++ b/app/Models/CashClose.php @@ -1,27 +1,31 @@ 'decimal:2', + 'initial_balance' => 'decimal:2', 'income' => 'decimal:2', 'exit' => 'decimal:2', - 'close_date' => 'date', + 'income_cash' => 'decimal:2', + 'income_card' => 'decimal:2', + 'income_transfer' => 'decimal:2', + 'opened_at' => 'datetime', + 'closed_at' => 'datetime', ]; /** @@ -55,26 +59,4 @@ 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; - } } diff --git a/app/Services/CashCloseService.php b/app/Services/CashCloseService.php index 5595bb5..7a9f5f4 100644 --- a/app/Services/CashCloseService.php +++ b/app/Services/CashCloseService.php @@ -12,17 +12,19 @@ class CashCloseService */ public static function getOrCreateOpenCashClose() { - $today = now()->format('Y-m-d'); - $cashClose = CashClose::open()->byDate($today)->first(); + $cashClose = CashClose::open()->first(); if (!$cashClose) { $cashClose = CashClose::create([ - 'close_date' => $today, + 'user_id' => Auth::id(), + 'opened_at' => now(), + 'initial_balance' => 0, 'income' => 0, 'exit' => 0, - 'balance' => 0, + 'income_cash' => 0, + 'income_card' => 0, + 'income_transfer' => 0, 'status' => 'open', - 'user_id' => Auth::id(), ]); } diff --git a/database/migrations/2025_11_05_114844_create_cash_closes_table.php b/database/migrations/2025_11_05_114844_create_cash_closes_table.php index e666632..b56d019 100644 --- a/database/migrations/2025_11_05_114844_create_cash_closes_table.php +++ b/database/migrations/2025_11_05_114844_create_cash_closes_table.php @@ -17,7 +17,7 @@ public function up(): void $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->enum('status', ['open', 'closed'])->default('open')->comment('Estado del corte'); $table->timestamp('close_date'); $table->timestamps(); }); diff --git a/database/migrations/2025_11_07_111354_modify_clients_table_rfc_email_constraints.php b/database/migrations/2025_11_07_111354_modify_clients_table_rfc_email_constraints.php new file mode 100644 index 0000000..d841275 --- /dev/null +++ b/database/migrations/2025_11_07_111354_modify_clients_table_rfc_email_constraints.php @@ -0,0 +1,24 @@ +string('rfc', 13)->unique()->change(); + $table->string('phone')->unique()->change(); + }); + } + + public function down(): void + { + // + } +}; diff --git a/database/migrations/2025_11_07_163831_modify_cash_closes_table_structure.php b/database/migrations/2025_11_07_163831_modify_cash_closes_table_structure.php new file mode 100644 index 0000000..6ea14a6 --- /dev/null +++ b/database/migrations/2025_11_07_163831_modify_cash_closes_table_structure.php @@ -0,0 +1,55 @@ +timestamp('opened_at')->nullable()->after('user_id'); + + $table->decimal('initial_balance', 10, 2)->default(0)->after('user_id'); + + $table->dropColumn('balance'); + + $table->decimal('income_cash', 10, 2)->default(0)->after('income'); + $table->decimal('income_card', 10, 2)->default(0)->after('income_cash'); + $table->decimal('income_transfer', 10, 2)->default(0)->after('income_card'); + }); + + // Renombrar y hacer nullable en una segunda operación + Schema::table('cash_closes', function (Blueprint $table) { + $table->renameColumn('close_date', 'closed_at'); + }); + + // Modificar closed_at para que sea nullable + Schema::table('cash_closes', function (Blueprint $table) { + $table->timestamp('closed_at')->nullable()->change(); + }); + } + + public function down(): void + { + Schema::table('cash_closes', function (Blueprint $table) { + $table->timestamp('closed_at')->nullable(false)->change(); + }); + + Schema::table('cash_closes', function (Blueprint $table) { + $table->renameColumn('closed_at', 'close_date'); + }); + + Schema::table('cash_closes', function (Blueprint $table) { + $table->dropColumn([ + 'opened_at', + 'initial_balance', + 'income_cash', + 'income_card', + 'income_transfer', + ]); + $table->decimal('balance', 10, 2)->after('user_id'); + }); + } +}; diff --git a/database/seeders/ClientSeeder.php b/database/seeders/ClientSeeder.php index e6eb3f7..6fbbe01 100644 --- a/database/seeders/ClientSeeder.php +++ b/database/seeders/ClientSeeder.php @@ -34,6 +34,7 @@ public function run(): void 'maternal' => 'Cruz', 'email' => 'maria.hernandez@example.com', 'phone' => '555-1003', + 'rfc' => 'MAHC910203XXX', ], [ 'name' => 'Carlos', @@ -41,6 +42,7 @@ public function run(): void 'maternal' => 'Ruiz', 'email' => 'carlos.sanchez@example.com', 'phone' => '555-1004', + 'rfc' => 'CASR910203XXX', ], [ 'name' => 'Laura', @@ -48,6 +50,7 @@ public function run(): void 'maternal' => 'Flores', 'email' => 'laura.gomez@example.com', 'phone' => '555-1005', + 'rfc' => 'LAGF910203XXX', ] ]; diff --git a/database/seeders/DevSeeder.php b/database/seeders/DevSeeder.php index ffea706..4538194 100644 --- a/database/seeders/DevSeeder.php +++ b/database/seeders/DevSeeder.php @@ -7,9 +7,9 @@ /** * Seeder de desarrollo - * + * * @author Moisés Cortés C. - * + * * @version 1.0.0 */ class DevSeeder extends Seeder @@ -22,5 +22,9 @@ public function run(): void $this->call(RoleSeeder::class); $this->call(UserSeeder::class); $this->call(SettingSeeder::class); + + $this->call(ClientSeeder::class); + $this->call(SimCardSeeder::class); + $this->call(PackageSeeder::class); } } diff --git a/docker-compose.yml b/docker-compose.yml index eda8317..ecdad7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,9 +19,9 @@ services: mem_limit: 512m extra_hosts: - "host.docker.internal:host-gateway" -# depends_on: -# mysql: -# condition: service_healthy + depends_on: + mysql: + condition: service_healthy nginx: image: nginx:alpine @@ -36,37 +36,37 @@ services: depends_on: - netbien-backend -# mysql: -# image: mysql:8.0 -# environment: -# MYSQL_DATABASE: ${DB_DATABASE} -# MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} -# MYSQL_PASSWORD: ${DB_PASSWORD} -# MYSQL_USER: ${DB_USERNAME} -# ports: -# - "${DB_PORT}:3306" -# volumes: -# - mysql_data:/var/lib/mysql -# networks: -# - netbien-network -# mem_limit: 512m -# healthcheck: -# test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] -# timeout: 15s -# retries: 10 + mysql: + image: mysql:8.0 + environment: + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_USER: ${DB_USERNAME} + ports: + - "${DB_PORT}:3306" + volumes: + - mysql_data:/var/lib/mysql + networks: + - netbien-network + mem_limit: 512m + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 15s + retries: 10 -# phpmyadmin: -# image: phpmyadmin/phpmyadmin -# environment: -# PMA_HOST: mysql -# PMA_PORT: 3306 -# ports: -# - "${PMA_PORT}:80" -# depends_on: -# - mysql -# networks: -# - netbien-network -# mem_limit: 512m + phpmyadmin: + image: phpmyadmin/phpmyadmin + environment: + PMA_HOST: mysql + PMA_PORT: 3306 + ports: + - "${PMA_PORT}:80" + depends_on: + - mysql + networks: + - netbien-network + mem_limit: 512m volumes: mysql_data: diff --git a/routes/api.php b/routes/api.php index 1b2a729..5f3e2c9 100644 --- a/routes/api.php +++ b/routes/api.php @@ -28,12 +28,13 @@ Route::resource('packages', PackagesController::class); + Route::get('clients/search', [ClientController::class, 'search']); 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::put('cash-closes/close', [CashCloseController::class, 'CloseCashClose']); Route::get('cash-closes/{id}/report', [CashCloseController::class, 'report']); });