plantation-tree.service.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
  2. import { PlantationNodeData, Worker, Task } from './plantation-node-data.interface';
  3. import { TreeNode, TreeService } from 'src/services/tree.service';
  4. @Injectable()
  5. export class PlantationTreeService extends TreeService<PlantationNodeData> {
  6. private workerIndex: Map<string, Worker> = new Map();
  7. // plantation-tree.service.ts (constructor only)
  8. constructor() {
  9. super({
  10. id: 'root',
  11. data: {
  12. id: 'root',
  13. name: 'Plantation Root',
  14. type: 'ROOT', // <-- ensure ROOT here
  15. status: 'ACTIVE',
  16. workers: [],
  17. },
  18. });
  19. }
  20. /** 🔍 Find a node by name (case-insensitive) */
  21. findNodeByName(name: string): TreeNode<PlantationNodeData> | undefined {
  22. let found: TreeNode<PlantationNodeData> | undefined;
  23. this.traverseTree(node => {
  24. if (node.data.name.toLowerCase() === name.toLowerCase()) found = node;
  25. });
  26. return found;
  27. }
  28. /** 👷 Assign worker to ZONE or BLOCK (not TREE), propagates upward */
  29. assignWorker(nodeId: string, worker: Worker): TreeNode<PlantationNodeData> {
  30. // 1️⃣ Check uniqueness globally
  31. if (this.workerIndex.has(worker.id)) {
  32. throw new BadRequestException(`Worker ID ${worker.id} already exists`);
  33. }
  34. const node = this.root.first(n => n.model.id === nodeId);
  35. if (!node) throw new NotFoundException(`Node ${nodeId} not found`);
  36. if (!node.model.data.workers) node.model.data.workers = [];
  37. node.model.data.workers.push(worker);
  38. // 2️⃣ Propagate upward to ancestors
  39. let current = node.parent;
  40. while (current) {
  41. if (!current.model.data.workers) current.model.data.workers = [];
  42. const parentHasWorker = current.model.data.workers.some(w => w.id === worker.id);
  43. if (!parentHasWorker) current.model.data.workers.push(worker);
  44. current = current.parent;
  45. }
  46. // 3️⃣ Update global index
  47. this.workerIndex.set(worker.id, worker);
  48. return node.model;
  49. }
  50. /** 🌳 Assign tree to an existing worker (must exist in ancestor chain) */
  51. assignTreeToWorker(treeId: string, workerId: string): TreeNode<PlantationNodeData> {
  52. const treeNode = this.root.first(n => n.model.id === treeId);
  53. if (!treeNode) throw new NotFoundException(`Tree node ${treeId} not found`);
  54. if (treeNode.model.data.type !== 'TREE') {
  55. throw new BadRequestException(`Only TREE nodes can be assigned to workers`);
  56. }
  57. // Search upward for worker existence
  58. let current = treeNode.parent;
  59. let foundWorker: Worker | null = null;
  60. while (current) {
  61. const worker = current.model.data.workers?.find(w => w.id === workerId);
  62. if (worker) {
  63. foundWorker = worker;
  64. break;
  65. }
  66. current = current.parent;
  67. }
  68. if (!foundWorker) {
  69. throw new NotFoundException(`Worker ${workerId} not found in ancestor hierarchy`);
  70. }
  71. // Assign minimal worker data to tree node
  72. treeNode.model.data.workers = [
  73. {
  74. id: foundWorker.id,
  75. name: foundWorker.name,
  76. personCode: foundWorker.personCode,
  77. role: foundWorker.role,
  78. DOB: foundWorker.DOB,
  79. age: foundWorker.age,
  80. nationality: foundWorker.nationality,
  81. },
  82. ];
  83. return treeNode.model;
  84. }
  85. /** 📋 Assign a task to a worker under a node */
  86. assignTaskToWorkerById(workerId: string, task: Task): Worker {
  87. const worker = this.workerIndex.get(workerId);
  88. if (!worker) throw new NotFoundException(`Worker ${workerId} not found`);
  89. if (!worker.assignedTasks) worker.assignedTasks = [];
  90. worker.assignedTasks.push(task);
  91. return worker;
  92. }
  93. /** 🌲 Recursively collect all workers under a node (aggregated view) */
  94. getWorkersUnderNode(nodeId: string): Worker[] {
  95. const start = this.root.first(n => n.model.id === nodeId);
  96. if (!start) throw new NotFoundException(`Node ${nodeId} not found`);
  97. const workers: Worker[] = [];
  98. start.walk(n => {
  99. if (n.model.data.workers) workers.push(...n.model.data.workers);
  100. return true;
  101. });
  102. return workers;
  103. }
  104. /** ✅ Update node metadata safely */
  105. updateMetadata(nodeId: string, metadata: Record<string, any>) {
  106. const node = this.root.first(n => n.model.id === nodeId);
  107. if (!node) throw new NotFoundException(`Node ${nodeId} not found`);
  108. node.model.data.metadata = { ...node.model.data.metadata, ...metadata };
  109. return node.model;
  110. }
  111. getWorkerById(workerId: string): Worker {
  112. const worker = this.workerIndex.get(workerId);
  113. if (!worker) throw new NotFoundException(`Worker ${workerId} not found`);
  114. return worker;
  115. }
  116. }