311 lines
14 KiB
PHP
311 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Modules\IndustrialEngineering\Services;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use InvalidArgumentException;
|
|
use Modules\IndustrialEngineering\Models\Bom;
|
|
use Modules\IndustrialEngineering\Models\BomLine;
|
|
use Modules\IndustrialEngineering\Models\BomStructureTemplate;
|
|
|
|
class BomTemplateService
|
|
{
|
|
public function __construct(
|
|
protected BomTreeService $bomTreeService
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Built-in structure presets (no DB row required).
|
|
*/
|
|
public function builtinPresets(): array
|
|
{
|
|
return [
|
|
'air_section_full' => [
|
|
'key' => 'air_section_full',
|
|
'name' => __('industrialengineering::lang.preset_air_section_full'),
|
|
'description' => __('industrialengineering::lang.preset_air_section_full_desc'),
|
|
'category' => 'air',
|
|
'nodes' => [
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_air_section_name'),
|
|
'item_type' => 'assembly',
|
|
'position_code' => 'M01-AIR',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_pump_name'),
|
|
'item_type' => 'subassembly',
|
|
'position_code' => 'M01-PMP',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_motor_electric'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_motor_gas'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_pressure_sensor'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_check_valve'), 'item_type' => 'part', 'quantity' => 1],
|
|
],
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_dryer_name'),
|
|
'item_type' => 'subassembly',
|
|
'position_code' => 'M01-DRY',
|
|
'quantity' => 1,
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_tank_name'),
|
|
'item_type' => 'subassembly',
|
|
'position_code' => 'M01-TNK',
|
|
'quantity' => 1,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'air_section_children' => [
|
|
'key' => 'air_section_children',
|
|
'name' => __('industrialengineering::lang.preset_air_children'),
|
|
'description' => __('industrialengineering::lang.preset_air_children_desc'),
|
|
'category' => 'air',
|
|
'nodes' => [
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_pump_name'),
|
|
'item_type' => 'subassembly',
|
|
'position_code' => 'M01-PMP',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_motor_electric'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_motor_gas'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_pressure_sensor'), 'item_type' => 'part', 'quantity' => 1],
|
|
],
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_dryer_name'),
|
|
'item_type' => 'subassembly',
|
|
'position_code' => 'M01-DRY',
|
|
'quantity' => 1,
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_tank_name'),
|
|
'item_type' => 'subassembly',
|
|
'position_code' => 'M01-TNK',
|
|
'quantity' => 1,
|
|
],
|
|
],
|
|
],
|
|
'pump_kit' => [
|
|
'key' => 'pump_kit',
|
|
'name' => __('industrialengineering::lang.preset_pump_kit'),
|
|
'description' => __('industrialengineering::lang.preset_pump_kit_desc'),
|
|
'category' => 'pump',
|
|
'nodes' => [
|
|
['name' => __('industrialengineering::lang.preset_motor_electric'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_motor_gas'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_pressure_sensor'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_check_valve'), 'item_type' => 'part', 'quantity' => 1],
|
|
],
|
|
],
|
|
'compressor_station_20' => [
|
|
'key' => 'compressor_station_20',
|
|
'name' => __('industrialengineering::lang.preset_compressor_station_20'),
|
|
'description' => __('industrialengineering::lang.preset_compressor_station_20_desc'),
|
|
'category' => 'air',
|
|
'nodes' => [
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_air_pump'),
|
|
'item_type' => 'device',
|
|
'position_code' => 'DEV-PMP',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_motor_55kw'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_pump_impeller'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_air_filter'), 'item_type' => 'consumable', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_mechanical_seal'), 'item_type' => 'consumable', 'quantity' => 1],
|
|
],
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_industrial_vacuum'),
|
|
'item_type' => 'device',
|
|
'position_code' => 'DEV-VAC',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_vacuum_pump'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_inlet_filter'), 'item_type' => 'consumable', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_solenoid_valve'), 'item_type' => 'part', 'quantity' => 1],
|
|
],
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_piston_compressor'),
|
|
'item_type' => 'device',
|
|
'position_code' => 'DEV-PST',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_cylinder'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_connecting_pipe'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_oil_filter'), 'item_type' => 'consumable', 'quantity' => 1],
|
|
],
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_screw_compressor'),
|
|
'item_type' => 'device',
|
|
'position_code' => 'DEV-SCR',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_rotor'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_separator'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_oil_filter'), 'item_type' => 'consumable', 'quantity' => 1],
|
|
],
|
|
],
|
|
[
|
|
'name' => __('industrialengineering::lang.preset_dryer_assembly'),
|
|
'item_type' => 'assembly',
|
|
'position_code' => 'ASM-DRY',
|
|
'quantity' => 1,
|
|
'children' => [
|
|
['name' => __('industrialengineering::lang.preset_refrigerant_dryer'), 'item_type' => 'part', 'quantity' => 1],
|
|
['name' => __('industrialengineering::lang.preset_condensate_drain'), 'item_type' => 'part', 'quantity' => 1],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
public function listForBusiness(int $businessId): array
|
|
{
|
|
$builtins = array_values($this->builtinPresets());
|
|
|
|
$saved = BomStructureTemplate::forBusiness($businessId)
|
|
->where('is_system', false)
|
|
->orderByDesc('updated_at')
|
|
->get()
|
|
->map(fn (BomStructureTemplate $t) => [
|
|
'id' => $t->id,
|
|
'key' => 'saved:'.$t->id,
|
|
'name' => $t->name,
|
|
'description' => $t->description,
|
|
'category' => $t->category,
|
|
'is_saved' => true,
|
|
'updated_at' => $t->updated_at?->toDateTimeString(),
|
|
])
|
|
->all();
|
|
|
|
return [
|
|
'builtin' => $builtins,
|
|
'saved' => $saved,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Apply preset key or saved template id under a parent BOM line.
|
|
*
|
|
* @return array{created_count:int, lines:array<int, BomLine>}
|
|
*/
|
|
public function apply(Bom $bom, ?int $parentLineId, string $templateKey): array
|
|
{
|
|
$nodes = $this->resolveTemplateNodes($bom->business_id, $templateKey);
|
|
|
|
return $this->bulkAddNodes($bom, $parentLineId, $nodes);
|
|
}
|
|
|
|
/**
|
|
* Recursively add multiple nodes under a parent.
|
|
*
|
|
* @param array<int, array<string, mixed>> $nodes
|
|
* @return array{created_count:int, lines:array<int, BomLine>}
|
|
*/
|
|
public function bulkAddNodes(Bom $bom, ?int $parentLineId, array $nodes): array
|
|
{
|
|
return DB::transaction(function () use ($bom, $parentLineId, $nodes) {
|
|
$created = [];
|
|
foreach ($nodes as $nodeDef) {
|
|
$created = array_merge($created, $this->addNodeRecursive($bom, $parentLineId, $nodeDef));
|
|
}
|
|
|
|
return [
|
|
'created_count' => count($created),
|
|
'lines' => $created,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function saveFromBomLine(BomLine $line, string $name, ?string $description = null, ?string $category = 'custom'): BomStructureTemplate
|
|
{
|
|
$structure = $this->exportLineSubtree($line);
|
|
|
|
return BomStructureTemplate::create([
|
|
'business_id' => $line->business_id,
|
|
'name' => $name,
|
|
'description' => $description,
|
|
'category' => $category ?: 'custom',
|
|
'structure_json' => ['nodes' => [$structure]],
|
|
'is_system' => false,
|
|
'created_by' => auth()->id(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Export a BOM line and its descendants to a portable node definition.
|
|
*/
|
|
public function exportLineSubtree(BomLine $line): array
|
|
{
|
|
$line->loadMissing(['itemMaster', 'children.itemMaster']);
|
|
|
|
$item = $line->itemMaster;
|
|
|
|
$def = [
|
|
'name' => $item?->name,
|
|
'item_type' => $item?->item_type ?? 'part',
|
|
'item_code' => $item?->item_code,
|
|
'quantity' => (float) $line->quantity,
|
|
'position_code' => $line->position_code,
|
|
'find_number' => $line->find_number,
|
|
];
|
|
|
|
if ($line->children->isNotEmpty()) {
|
|
$def['children'] = $line->children
|
|
->sortBy('sort_order')
|
|
->map(fn (BomLine $child) => $this->exportLineSubtree($child))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
return $def;
|
|
}
|
|
|
|
protected function resolveTemplateNodes(int $businessId, string $templateKey): array
|
|
{
|
|
if (str_starts_with($templateKey, 'saved:')) {
|
|
$id = (int) substr($templateKey, 6);
|
|
$template = BomStructureTemplate::forBusiness($businessId)->findOrFail($id);
|
|
$json = $template->structure_json ?? [];
|
|
|
|
return $json['nodes'] ?? (is_array($json) && isset($json[0]) ? $json : []);
|
|
}
|
|
|
|
$presets = $this->builtinPresets();
|
|
if (! isset($presets[$templateKey])) {
|
|
throw new InvalidArgumentException(__('industrialengineering::lang.template_not_found'));
|
|
}
|
|
|
|
return $presets[$templateKey]['nodes'] ?? [];
|
|
}
|
|
|
|
/**
|
|
* @return array<int, BomLine>
|
|
*/
|
|
protected function addNodeRecursive(Bom $bom, ?int $parentLineId, array $nodeDef): array
|
|
{
|
|
$children = $nodeDef['children'] ?? [];
|
|
unset($nodeDef['children']);
|
|
|
|
$line = $this->bomTreeService->addNode($bom, $parentLineId, $nodeDef);
|
|
$created = [$line];
|
|
|
|
foreach ($children as $childDef) {
|
|
$created = array_merge($created, $this->addNodeRecursive($bom, $line->id, $childDef));
|
|
}
|
|
|
|
return $created;
|
|
}
|
|
}
|