authorizeIe('ie.bom.view'); $bom = Bom::forBusiness($this->businessId())->findOrFail($bomId); return response()->json([ 'success' => true, 'tree' => $this->bomTreeService->buildEditorTree($bom), ]); } public function storeNode(Request $request, $bomId) { $this->authorizeIe('ie.bom.create'); $bom = Bom::forBusiness($this->businessId())->findOrFail($bomId); $data = $request->validate([ 'parent_line_id' => 'nullable|exists:ie_bom_lines,id', 'item_master_id' => 'nullable|exists:ie_item_masters,id', 'item_code' => 'nullable|string|max:100', 'name' => 'required_without:item_master_id|string|max:255', 'item_type' => 'nullable|string', 'quantity' => 'nullable|numeric|min:0.0001', 'position_code' => 'nullable|string|max:100', 'find_number' => 'nullable|string|max:100', 'variation_id' => 'nullable|integer', 'product_id' => 'nullable|integer', 'standard_cost' => 'nullable|numeric|min:0', 'is_serviceable' => 'nullable|boolean', 'notes' => 'nullable|string', ]); try { $line = $this->bomTreeService->addNode( $bom, $data['parent_line_id'] ?? null, $data ); } catch (InvalidArgumentException $e) { return response()->json([ 'success' => false, 'msg' => $e->getMessage(), ], 422); } $integrationMessages = $this->autoIntegration->afterBomNodeCreated($line->itemMaster, $data); return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.node_added'), 'integration_messages' => $integrationMessages, 'node' => $this->bomTreeService->lineToEditorNode($line->load(['itemMaster', 'children'])), ]); } public function updateNode(Request $request, $lineId) { $this->authorizeIe('ie.bom.create'); $line = BomLine::forBusiness($this->businessId())->findOrFail($lineId); $data = $request->validate([ 'quantity' => 'nullable|numeric|min:0.0001', 'position_code' => 'nullable|string|max:100', 'find_number' => 'nullable|string|max:100', 'standard_cost' => 'nullable|numeric|min:0', 'is_serviceable' => 'nullable|boolean', 'notes' => 'nullable|string', 'variation_id' => 'nullable|integer', 'product_id' => 'nullable|integer', ]); $line = $this->bomTreeService->updateNode($line, $data); return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.saved'), 'node' => $this->bomTreeService->lineToEditorNode($line), ]); } public function move(Request $request, $bomId) { $this->authorizeIe('ie.bom.create'); $bom = Bom::forBusiness($this->businessId())->findOrFail($bomId); $data = $request->validate([ 'nestable' => 'required|array', ]); try { $this->bomTreeService->applyNestable($bom, $data['nestable']); } catch (InvalidArgumentException $e) { return response()->json([ 'success' => false, 'msg' => $e->getMessage(), ], 422); } return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.tree_saved'), 'tree' => $this->bomTreeService->buildEditorTree($bom->fresh()), ]); } public function destroyNode($lineId) { $this->authorizeIe('ie.bom.create'); $line = BomLine::forBusiness($this->businessId())->findOrFail($lineId); $this->bomTreeService->deleteNode($line); return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.node_deleted'), ]); } public function duplicateBranch(Request $request, $bomId) { $this->authorizeIe('ie.bom.create'); $bom = Bom::forBusiness($this->businessId())->findOrFail($bomId); $data = $request->validate([ 'source_line_id' => 'required|exists:ie_bom_lines,id', 'target_parent_line_id' => 'nullable|exists:ie_bom_lines,id', ]); $sourceLine = BomLine::forBusiness($this->businessId())->findOrFail($data['source_line_id']); try { $result = $this->bomTreeService->duplicateSubtree( $bom, $sourceLine, $data['target_parent_line_id'] ?? null ); } catch (InvalidArgumentException $e) { return response()->json(['success' => false, 'msg' => $e->getMessage()], 422); } return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.branch_pasted', ['count' => $result['created_count']]), 'created_count' => $result['created_count'], 'tree' => $this->bomTreeService->buildEditorTree($bom->fresh()), ]); } public function listTemplates($bomId) { $this->authorizeIe('ie.bom.view'); Bom::forBusiness($this->businessId())->findOrFail($bomId); return response()->json([ 'success' => true, 'templates' => $this->bomTemplateService->listForBusiness($this->businessId()), ]); } public function applyTemplate(Request $request, $bomId) { $this->authorizeIe('ie.bom.create'); $bom = Bom::forBusiness($this->businessId())->findOrFail($bomId); $data = $request->validate([ 'parent_line_id' => 'nullable|exists:ie_bom_lines,id', 'template_key' => 'required|string|max:100', ]); try { $result = $this->bomTemplateService->apply( $bom, $data['parent_line_id'] ?? null, $data['template_key'] ); } catch (InvalidArgumentException $e) { return response()->json(['success' => false, 'msg' => $e->getMessage()], 422); } return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.bulk_nodes_added', ['count' => $result['created_count']]), 'created_count' => $result['created_count'], 'tree' => $this->bomTreeService->buildEditorTree($bom->fresh()), ]); } public function bulkAddNodes(Request $request, $bomId) { $this->authorizeIe('ie.bom.create'); $bom = Bom::forBusiness($this->businessId())->findOrFail($bomId); $data = $request->validate([ 'parent_line_id' => 'nullable|exists:ie_bom_lines,id', 'nodes' => 'required|array|min:1', 'nodes.*.name' => 'required|string|max:255', 'nodes.*.item_type' => 'nullable|string', 'nodes.*.quantity' => 'nullable|numeric|min:0.0001', 'nodes.*.position_code' => 'nullable|string|max:100', 'nodes.*.find_number' => 'nullable|string|max:100', 'nodes.*.children' => 'nullable|array', ]); try { $result = $this->bomTemplateService->bulkAddNodes( $bom, $data['parent_line_id'] ?? null, $data['nodes'] ); } catch (InvalidArgumentException $e) { return response()->json(['success' => false, 'msg' => $e->getMessage()], 422); } return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.bulk_nodes_added', ['count' => $result['created_count']]), 'created_count' => $result['created_count'], 'tree' => $this->bomTreeService->buildEditorTree($bom->fresh()), ]); } public function saveTemplate(Request $request) { $this->authorizeIe('ie.bom.create'); $data = $request->validate([ 'bom_line_id' => 'required|exists:ie_bom_lines,id', 'name' => 'required|string|max:255', 'description' => 'nullable|string|max:1000', 'category' => 'nullable|string|max:50', ]); $line = BomLine::forBusiness($this->businessId())->findOrFail($data['bom_line_id']); $template = $this->bomTemplateService->saveFromBomLine( $line, $data['name'], $data['description'] ?? null, $data['category'] ?? 'custom' ); return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.template_saved'), 'template' => [ 'id' => $template->id, 'key' => 'saved:'.$template->id, 'name' => $template->name, ], ]); } public function destroyTemplate($id) { $this->authorizeIe('ie.bom.create'); $template = BomStructureTemplate::forBusiness($this->businessId())->findOrFail($id); $template->delete(); return response()->json([ 'success' => true, 'msg' => __('industrialengineering::lang.template_deleted'), ]); } public function quickCreateItem(Request $request) { $this->authorizeIe('ie.items.create'); $data = $request->validate([ 'item_code' => 'nullable|string|max:100', 'name' => 'required|string|max:255', 'item_type' => 'required|string', 'variation_id' => 'nullable|integer', 'product_id' => 'nullable|integer', 'standard_cost' => 'nullable|numeric|min:0', ]); $codes = $this->resolveCodes('item', $data['item_code'] ?? null, new ItemMaster(), 'item_code'); $item = ItemMaster::create(array_merge($data, $codes, [ 'business_id' => $this->businessId(), 'is_serviceable' => true, ])); $this->erpItemLink->linkItem($item, $data); return response()->json([ 'success' => true, 'item' => [ 'id' => $item->id, 'item_code' => $item->item_code, 'name' => $item->name, 'item_type' => $item->item_type, ], ]); } }