pdv.backend/app/Services/BundleService.php

176 lines
5.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\Bundle;
use App\Models\BundleItem;
use App\Models\BundlePrice;
use Illuminate\Support\Facades\DB;
class BundleService
{
/**
* Crear un nuevo kit/bundle con sus componentes y precio
*/
public function createBundle(array $data): Bundle
{
return DB::transaction(function () use ($data) {
// 1. Crear el bundle principal
$bundle = Bundle::create([
'name' => $data['name'],
'sku' => $data['sku'],
'barcode' => $data['barcode'] ?? null,
]);
// 2. Agregar componentes al kit
foreach ($data['items'] as $item) {
BundleItem::create([
'bundle_id' => $bundle->id,
'inventory_id' => $item['inventory_id'],
'quantity' => $item['quantity'],
]);
}
// 3. Calcular costos y crear precio
$bundle->load('items.inventory.price');
$totalCost = 0;
$totalRetailPrice = 0;
foreach ($bundle->items as $item) {
$totalCost += ($item->inventory->price->cost ?? 0) * $item->quantity;
$totalRetailPrice += ($item->inventory->price->retail_price ?? 0) * $item->quantity;
}
// Permitir override de precio (para promociones)
$finalRetailPrice = $data['retail_price'] ?? $totalRetailPrice;
$tax = $data['tax'] ?? 16.00;
BundlePrice::create([
'bundle_id' => $bundle->id,
'cost' => $totalCost,
'retail_price' => $finalRetailPrice,
'tax' => $tax,
]);
return $bundle->fresh(['items.inventory.price', 'price']);
});
}
/**
* Actualizar un kit existente
*/
public function updateBundle(Bundle $bundle, array $data): Bundle
{
return DB::transaction(function () use ($bundle, $data) {
// 1. Actualizar datos básicos del bundle
$bundle->update([
'name' => $data['name'] ?? $bundle->name,
'sku' => $data['sku'] ?? $bundle->sku,
'barcode' => $data['barcode'] ?? $bundle->barcode,
]);
// 2. Actualizar componentes si se proporcionan
if (isset($data['items'])) {
// Eliminar componentes actuales
$bundle->items()->delete();
// Crear nuevos componentes
foreach ($data['items'] as $item) {
BundleItem::create([
'bundle_id' => $bundle->id,
'inventory_id' => $item['inventory_id'],
'quantity' => $item['quantity'],
]);
}
}
// 3. Recalcular o actualizar precio
if (isset($data['recalculate_price']) && $data['recalculate_price']) {
// Recalcular precio basado en componentes actuales
$bundle->load('items.inventory.price');
$totalCost = 0;
$totalRetailPrice = 0;
foreach ($bundle->items as $item) {
$totalCost += ($item->inventory->price->cost ?? 0) * $item->quantity;
$totalRetailPrice += ($item->inventory->price->retail_price ?? 0) * $item->quantity;
}
$finalRetailPrice = $data['retail_price'] ?? $totalRetailPrice;
$tax = $data['tax'] ?? 16.00;
$bundle->price->update([
'cost' => $totalCost,
'retail_price' => $finalRetailPrice,
'tax' => $tax,
]);
} elseif (isset($data['retail_price'])) {
// Solo actualizar precio sin recalcular componentes
$bundle->price->update([
'retail_price' => $data['retail_price'],
'tax' => $data['tax'] ?? 16.00,
]);
}
return $bundle->fresh(['items.inventory.price', 'price']);
});
}
/**
* Validar disponibilidad de stock del kit
*/
public function validateBundleAvailability(Bundle $bundle, int $quantity, ?int $warehouseId = null): void
{
if (!$bundle->hasStock($quantity, $warehouseId)) {
$availableStock = $warehouseId
? $bundle->stockInWarehouse($warehouseId)
: $bundle->available_stock;
throw new \Exception(
"Stock insuficiente del kit '{$bundle->name}'. " .
"Disponibles: {$availableStock}, Requeridos: {$quantity}"
);
}
}
/**
* Obtener precio total de componentes (sin promoción)
*/
public function getComponentsValue(Bundle $bundle): float
{
$total = 0;
foreach ($bundle->items as $item) {
$componentPrice = $item->inventory->price->retail_price ?? 0;
$total += $componentPrice * $item->quantity;
}
return round($total, 2);
}
/**
* Eliminar (soft delete) un bundle
*/
public function deleteBundle(Bundle $bundle): bool
{
return $bundle->delete();
}
/**
* Restaurar un bundle eliminado
*/
public function restoreBundle(int $bundleId): ?Bundle
{
$bundle = Bundle::withTrashed()->find($bundleId);
if ($bundle && $bundle->trashed()) {
$bundle->restore();
return $bundle->fresh(['items.inventory.price', 'price']);
}
return null;
}
}