pdv.backend/app/Services/SaleService.php

121 lines
3.9 KiB
PHP

<?php namespace App\Services;
use App\Models\CashRegister;
use App\Models\Sale;
use App\Models\SaleDetail;
use App\Models\Inventory;
use Illuminate\Support\Facades\DB;
class SaleService
{
/**
* Crear una nueva venta con sus detalles
*
*/
public function createSale(array $data)
{
return DB::transaction(function () use ($data) {
// Calcular el cambio si es pago en efectivo
$cashReceived = null;
$change = null;
if ($data['payment_method'] === 'cash' && isset($data['cash_received'])) {
$cashReceived = $data['cash_received'];
$change = $cashReceived - $data['total'];
}
// 1. Crear la venta principal
$sale = Sale::create([
'user_id' => $data['user_id'],
'cash_register_id' => $data['cash_register_id'] ?? $this->getCurrentCashRegister($data['user_id']),
'invoice_number' => $data['invoice_number'] ?? $this->generateInvoiceNumber(),
'subtotal' => $data['subtotal'],
'tax' => $data['tax'],
'total' => $data['total'],
'cash_received' => $cashReceived,
'change' => $change,
'payment_method' => $data['payment_method'],
'status' => $data['status'] ?? 'completed',
]);
// 2. Crear los detalles de la venta y actualizar stock
foreach ($data['items'] as $item) {
// Crear detalle de venta
SaleDetail::create([
'sale_id' => $sale->id,
'inventory_id' => $item['inventory_id'],
'product_name' => $item['product_name'],
'quantity' => $item['quantity'],
'unit_price' => $item['unit_price'],
'subtotal' => $item['subtotal'],
]);
// Descontar del stock
$inventory = Inventory::find($item['inventory_id']);
if ($inventory) {
$inventory->decrement('stock', $item['quantity']);
}
}
// 3. Retornar la venta con sus relaciones cargadas
return $sale->load(['details.inventory', 'user']);
});
}
/**
* Cancelar una venta y restaurar el stock
*
*/
public function cancelSale(Sale $sale)
{
return DB::transaction(function () use ($sale) {
// Verificar que la venta esté completada
if ($sale->status !== 'completed') {
throw new \Exception('Solo se pueden cancelar ventas completadas.');
}
// Restaurar stock de cada producto
foreach ($sale->details as $detail) {
$inventory = Inventory::find($detail->inventory_id);
if ($inventory) {
$inventory->increment('stock', $detail->quantity);
}
}
// Marcar venta como cancelada
$sale->update(['status' => 'cancelled']);
return $sale->fresh(['details.inventory', 'user']);
});
}
/**
* Generar número de factura único
* Formato: INV-YYYYMMDD-0001
*/
private function generateInvoiceNumber(): string
{
$prefix = 'INV-';
$date = now()->format('Ymd');
// Obtener la última venta del día
$lastSale = Sale::whereDate('created_at', today())
->orderBy('id', 'desc')
->first();
// Incrementar secuencial
$sequential = $lastSale ? (intval(substr($lastSale->invoice_number, -4)) + 1) : 1;
return $prefix . $date . '-' . str_pad($sequential, 4, '0', STR_PAD_LEFT);
}
private function getCurrentCashRegister($userId)
{
$register = CashRegister::where('user_id', $userId)
->where('status', 'open')
->first();
return $register ? $register->id : null;
}
}