176 lines
5.6 KiB
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;
|
|
}
|
|
}
|