128 lines
4.7 KiB
PHP
128 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace Modules\IndustrialEngineering\Services;
|
|
|
|
use Modules\IndustrialEngineering\Models\Bom;
|
|
use Modules\IndustrialEngineering\Models\CpqConfiguration;
|
|
use Modules\IndustrialEngineering\Models\CpqOption;
|
|
use Modules\IndustrialEngineering\Models\CpqOptionGroup;
|
|
use Modules\IndustrialEngineering\Models\CpqRule;
|
|
|
|
class CpqService
|
|
{
|
|
public function __construct(
|
|
protected LiveBomCostingService $liveBomCosting,
|
|
protected BomTreeService $bomTree
|
|
) {
|
|
}
|
|
|
|
public function getConfigurator(int $businessId, int $productPlatformId): array
|
|
{
|
|
$groups = CpqOptionGroup::forBusiness($businessId)
|
|
->where('product_platform_id', $productPlatformId)
|
|
->with('options')
|
|
->orderBy('sort_order')
|
|
->get();
|
|
|
|
return [
|
|
'groups' => $groups,
|
|
'rules' => CpqRule::forBusiness($businessId)->where('is_active', true)->orderBy('priority')->get(),
|
|
];
|
|
}
|
|
|
|
public function buildConfiguration(int $businessId, array $data): CpqConfiguration
|
|
{
|
|
$selectedOptions = $data['selected_options'] ?? [];
|
|
$bomSnapshot = $this->buildBomSnapshot($businessId, $data['base_bom_id'] ?? null, $selectedOptions);
|
|
$price = $this->calculatePrice($businessId, $bomSnapshot, $selectedOptions);
|
|
|
|
return CpqConfiguration::create([
|
|
'business_id' => $businessId,
|
|
'product_platform_id' => $data['product_platform_id'] ?? null,
|
|
'name' => $data['name'] ?? 'Configuration',
|
|
'ref_no' => $this->nextRef($businessId),
|
|
'status' => 'draft',
|
|
'contact_id' => $data['contact_id'] ?? null,
|
|
'selected_options' => $selectedOptions,
|
|
'bom_snapshot' => $bomSnapshot,
|
|
'quoted_price' => $price,
|
|
'created_by' => auth()->id(),
|
|
]);
|
|
}
|
|
|
|
public function applyRules(array $selectedOptionIds, int $businessId): array
|
|
{
|
|
$rules = CpqRule::forBusiness($businessId)->where('is_active', true)->orderBy('priority')->get();
|
|
$result = $selectedOptionIds;
|
|
|
|
foreach ($rules as $rule) {
|
|
$conditions = $rule->conditions ?? [];
|
|
$ifSelected = $conditions['if_option_ids'] ?? [];
|
|
if (empty($ifSelected) || count(array_intersect($ifSelected, $result)) === count($ifSelected)) {
|
|
foreach ($rule->actions['add_option_ids'] ?? [] as $addId) {
|
|
if (! in_array($addId, $result)) {
|
|
$result[] = $addId;
|
|
}
|
|
}
|
|
foreach ($rule->actions['remove_option_ids'] ?? [] as $removeId) {
|
|
$result = array_values(array_diff($result, [$removeId]));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function buildBomSnapshot(int $businessId, ?int $baseBomId, array $selectedOptionIds): array
|
|
{
|
|
$lines = [];
|
|
if ($baseBomId) {
|
|
$bom = Bom::forBusiness($businessId)->with('lines')->find($baseBomId);
|
|
if ($bom) {
|
|
$lines = $bom->lines->map(fn ($l) => [
|
|
'item_master_id' => $l->item_master_id,
|
|
'quantity' => $l->quantity,
|
|
])->all();
|
|
}
|
|
}
|
|
|
|
$options = CpqOption::forBusiness($businessId)->whereIn('id', $selectedOptionIds)->get();
|
|
foreach ($options as $option) {
|
|
foreach ($option->bom_additions ?? [] as $add) {
|
|
$lines[] = $add;
|
|
}
|
|
foreach ($option->bom_removals ?? [] as $remove) {
|
|
$lines = array_filter($lines, fn ($l) => ($l['item_master_id'] ?? null) != ($remove['item_master_id'] ?? null));
|
|
}
|
|
}
|
|
|
|
return ['lines' => array_values($lines), 'base_bom_id' => $baseBomId];
|
|
}
|
|
|
|
protected function calculatePrice(int $businessId, array $bomSnapshot, array $selectedOptionIds): float
|
|
{
|
|
$price = 0.0;
|
|
if (! empty($bomSnapshot['base_bom_id'])) {
|
|
$bom = Bom::forBusiness($businessId)->find($bomSnapshot['base_bom_id']);
|
|
if ($bom) {
|
|
$costing = $this->liveBomCosting->analyze($bom, 1);
|
|
$price += (float) ($costing['totals']['live_material'] ?? 0);
|
|
}
|
|
}
|
|
|
|
$options = CpqOption::forBusiness($businessId)->whereIn('id', $selectedOptionIds)->get();
|
|
foreach ($options as $option) {
|
|
$price += (float) $option->price_delta;
|
|
}
|
|
|
|
return round($price, 4);
|
|
}
|
|
|
|
protected function nextRef(int $businessId): string
|
|
{
|
|
$count = CpqConfiguration::forBusiness($businessId)->withTrashed()->count() + 1;
|
|
|
|
return 'CPQ-'.str_pad((string) $count, 5, '0', STR_PAD_LEFT);
|
|
}
|
|
}
|