import { Injectable, NotFoundException, BadRequestException, Inject, forwardRef } from '@nestjs/common'; import { FFBLangChainService } from '../../FFB/services/ffb-langchain.service'; import { MongoCoreService } from '../../mongo/mongo-core.service'; import { BlockRepository } from '../repositories/block.repository'; import { Block } from '../schemas/site.schema'; import { PhaseRepository } from '../repositories/phase.repository'; import { SiteRepository } from '../repositories/site.repository'; import { FFBProductionService } from '../../FFB/services/ffb-production.service'; @Injectable() export class BlockService { private repo: BlockRepository; private phaseRepo: PhaseRepository; private siteRepo: SiteRepository; constructor( private readonly mongoCore: MongoCoreService, @Inject(forwardRef(() => FFBProductionService)) private readonly ffbService: FFBProductionService, private readonly ffbLangChainService: FFBLangChainService, ) { } private async getRepos() { if (!this.repo) { const db = await this.mongoCore.getDb(); this.repo = new BlockRepository(db); this.phaseRepo = new PhaseRepository(db); this.siteRepo = new SiteRepository(db); await this.repo.init(); await this.phaseRepo.init(); await this.siteRepo.init(); } return { repo: this.repo, phaseRepo: this.phaseRepo, siteRepo: this.siteRepo }; } async create(block: Block): Promise { const { repo, phaseRepo, siteRepo } = await this.getRepos(); // 1. Validate Phase exists const phase = await phaseRepo.findById(block.phaseId); if (!phase) { throw new BadRequestException(`Phase with ID ${block.phaseId} does not exist.`); } // 2. Validate Site exists const site = await siteRepo.findById(phase.siteId); if (!site) { throw new BadRequestException(`Site with ID ${phase.siteId} (from Phase ${block.phaseId}) does not exist.`); } return repo.create(block); } async findAll(filter: Record = {}): Promise { const { repo } = await this.getRepos(); return repo.findAll(filter); } async findById(id: string): Promise { const { repo } = await this.getRepos(); return repo.findById(id); } async update(id: string, update: Partial): Promise { const { repo, phaseRepo, siteRepo } = await this.getRepos(); const block = await repo.findById(id); if (!block) throw new NotFoundException('Block not found'); if (update.phaseId) { // 1. Validate Phase exists const phase = await phaseRepo.findById(update.phaseId); if (!phase) throw new BadRequestException(`Phase with ID ${update.phaseId} does not exist.`); // 2. Validate Site exists const site = await siteRepo.findById(phase.siteId); if (!site) throw new BadRequestException(`Site with ID ${phase.siteId} (from Phase ${update.phaseId}) does not exist.`); } await repo.update(id, update); } async delete(id: string): Promise { const { repo } = await this.getRepos(); const block = await repo.findById(id); if (!block) throw new NotFoundException('Block not found'); // Cascading Delete: // 1. Delete all FFB Production records referencing this block await this.ffbService.deleteMany({ 'block.id': id }); // 2. Delete the block itself await repo.delete(id); } async generateDescription(data: { name: string, phaseName?: string, size?: number, numOfTrees?: number }): Promise<{ description: string }> { const prompt = `Write a realistic, professional description (2–4 sentences) for an oil palm plantation block named "${data.name}"${data.phaseName ? ` within phase "${data.phaseName}"` : ''}.${data.size ? ` It has a size of ${data.size} units` : ''}${data.numOfTrees ? ` and contains ${data.numOfTrees} trees` : ''}. Choose 1-2 aspects from the following to incorporate naturally: - Block health and maturity (e.g., trees at peak production age, healthy canopy development, consistent fruit set) - Field accessibility (e.g., easily accessible by tractor paths, slightly sloped area requiring specialized harvesting tools) - Environmental features (e.g., bordering primary forest, presence of natural irrigation channels, rich nutrient-holding soil) - Monitoring and care (e.g., subject to intensive pest monitoring, recently fertilized, under strict irrigation schedule) Guidelines: - Maintain a grounded, operational tone. - Do not mention the bullet points explicitly; weave them into the narrative. - Ensure the length is exactly 2-4 sentences. Description:`; const description = await this.ffbLangChainService.chatStateless(prompt); return { description: description.replace(/^Description:\s*/i, '').trim() }; } }