import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { PlantationNodeData, Worker, Task } from './plantation-node-data.interface'; import { TreeNode, TreeService } from 'src/services/tree.service'; @Injectable() export class PlantationTreeService extends TreeService { private workerIndex: Map = new Map(); // plantation-tree.service.ts (constructor only) constructor() { super({ id: 'root', data: { id: 'root', name: 'Plantation Root', type: 'ROOT', // <-- ensure ROOT here status: 'ACTIVE', workers: [], }, }); } /** 🔍 Find a node by name (case-insensitive) */ findNodeByName(name: string): TreeNode | undefined { let found: TreeNode | undefined; this.traverseTree(node => { if (node.data.name.toLowerCase() === name.toLowerCase()) found = node; }); return found; } /** 👷 Assign worker to ZONE or BLOCK (not TREE), propagates upward */ assignWorker(nodeId: string, worker: Worker): TreeNode { // 1️⃣ Check uniqueness globally if (this.workerIndex.has(worker.id)) { throw new BadRequestException(`Worker ID ${worker.id} already exists`); } const node = this.root.first(n => n.model.id === nodeId); if (!node) throw new NotFoundException(`Node ${nodeId} not found`); if (!node.model.data.workers) node.model.data.workers = []; node.model.data.workers.push(worker); // 2️⃣ Propagate upward to ancestors let current = node.parent; while (current) { if (!current.model.data.workers) current.model.data.workers = []; const parentHasWorker = current.model.data.workers.some(w => w.id === worker.id); if (!parentHasWorker) current.model.data.workers.push(worker); current = current.parent; } // 3️⃣ Update global index this.workerIndex.set(worker.id, worker); return node.model; } /** 🌳 Assign tree to an existing worker (must exist in ancestor chain) */ assignTreeToWorker(treeId: string, workerId: string): TreeNode { const treeNode = this.root.first(n => n.model.id === treeId); if (!treeNode) throw new NotFoundException(`Tree node ${treeId} not found`); if (treeNode.model.data.type !== 'TREE') { throw new BadRequestException(`Only TREE nodes can be assigned to workers`); } // Search upward for worker existence let current = treeNode.parent; let foundWorker: Worker | null = null; while (current) { const worker = current.model.data.workers?.find(w => w.id === workerId); if (worker) { foundWorker = worker; break; } current = current.parent; } if (!foundWorker) { throw new NotFoundException(`Worker ${workerId} not found in ancestor hierarchy`); } // Assign minimal worker data to tree node treeNode.model.data.workers = [ { id: foundWorker.id, name: foundWorker.name, personCode: foundWorker.personCode, role: foundWorker.role, DOB: foundWorker.DOB, age: foundWorker.age, nationality: foundWorker.nationality, }, ]; return treeNode.model; } /** 📋 Assign a task to a worker under a node */ assignTaskToWorkerById(workerId: string, task: Task): Worker { const worker = this.workerIndex.get(workerId); if (!worker) throw new NotFoundException(`Worker ${workerId} not found`); if (!worker.assignedTasks) worker.assignedTasks = []; worker.assignedTasks.push(task); return worker; } /** 🌲 Recursively collect all workers under a node (aggregated view) */ getWorkersUnderNode(nodeId: string): Worker[] { const start = this.root.first(n => n.model.id === nodeId); if (!start) throw new NotFoundException(`Node ${nodeId} not found`); const workers: Worker[] = []; start.walk(n => { if (n.model.data.workers) workers.push(...n.model.data.workers); return true; }); return workers; } /** ✅ Update node metadata safely */ updateMetadata(nodeId: string, metadata: Record) { const node = this.root.first(n => n.model.id === nodeId); if (!node) throw new NotFoundException(`Node ${nodeId} not found`); node.model.data.metadata = { ...node.model.data.metadata, ...metadata }; return node.model; } getWorkerById(workerId: string): Worker { const worker = this.workerIndex.get(workerId); if (!worker) throw new NotFoundException(`Worker ${workerId} not found`); return worker; } }