Преглед изворни кода

some updates for enhancements

Dr-Swopt пре 2 месеци
родитељ
комит
83102679b1

+ 78 - 0
src/activity/activity.controller.ts

@@ -0,0 +1,78 @@
+// src/harvest/harvest.controller.ts
+import { Controller, Get, Post, Body, Param, Put, Delete, Query } from '@nestjs/common';
+import { ActivityService } from './activity.service';
+import { CreateActivityDto, UpdateActivityDto } from './activity.dto';
+
+@Controller('activity')
+export class ActivityController {
+    constructor(private readonly activityService: ActivityService) { }
+
+    @Post()
+    async create(@Body() dto: CreateActivityDto) {
+        return this.activityService.create(dto);
+    }
+
+    @Get()
+    async findAll(@Query() query: Record<string, any>) {
+        return this.activityService.findAll(query);
+    }
+
+    @Get(':id')
+    async findOne(@Param('id') id: string) {
+        return this.activityService.findById(id);
+    }
+
+    @Put(':id')
+    async update(@Param('id') id: string, @Body() dto: UpdateActivityDto) {
+        return this.activityService.update(id, dto);
+    }
+
+    @Delete(':id')
+    async remove(@Param('id') id: string) {
+        return this.activityService.delete(id);
+    }
+
+    @Get('stats/resources')
+    async totalResources() {
+        return this.activityService.getTotalResourceHours();
+    }
+
+    @Get('stats/outputs')
+    async totalOutputs() {
+        return this.activityService.getTotalOutputWeight();
+    }
+
+    @Get('date-range')
+    async byDateRange(@Query('start') start: string, @Query('end') end: string) {
+        return this.activityService.findByDateRange(new Date(start), new Date(end));
+    }
+
+    @Get('stats/workers')
+    async totalWorkers(
+        @Query('start') start?: string,
+        @Query('end') end?: string,
+        @Query('location') location?: string,
+    ) {
+        const filter: any = {};
+        if (start && end) {
+            filter.start = new Date(start);
+            filter.end = new Date(end);
+        }
+        if (location) filter.location = location;
+        return this.activityService.getTotalWorkers(filter);
+    }
+
+    @Get('stats/outputs/detail')
+    async totalOutputsDetail(
+        @Query('start') start?: string,
+        @Query('end') end?: string,
+    ) {
+        const filter: any = {};
+        if (start && end) {
+            filter.start = new Date(start);
+            filter.end = new Date(end);
+        }
+        return this.activityService.getTotalOutputs(filter);
+    }
+
+}

+ 18 - 22
src/harvest/harvest.dto.ts → src/activity/activity.dto.ts

@@ -1,4 +1,3 @@
-// src/harvest/harvest.dto.ts
 import {
   IsString,
   IsNotEmpty,
@@ -22,6 +21,15 @@ class QuantityValueDto {
   uom: string;
 }
 
+// ✅ Moved this class ABOVE OutputDto
+class WeightValueDto {
+  @IsNumber()
+  weight: number;
+
+  @IsString()
+  uom: string;
+}
+
 class ResourceDto {
   @IsString()
   type: string;
@@ -58,14 +66,6 @@ class OutputDto {
   weightValue: WeightValueDto;
 }
 
-class WeightValueDto {
-  @IsNumber()
-  weight: number;
-
-  @IsString()
-  uom: string;
-}
-
 class TargetDto {
   @IsString()
   type: string;
@@ -83,10 +83,16 @@ class TargetDto {
 }
 
 /* ------------------------------
- * Main Harvest DTO
+ * Duration and Main DTOs
  * ------------------------------ */
 
-export class CreateHarvestDto {
+class DurationDto {
+  @ValidateNested()
+  @Type(() => QuantityValueDto)
+  value: QuantityValueDto;
+}
+
+export class CreateActivityDto {
   @IsString()
   @IsNotEmpty()
   name: string;
@@ -121,17 +127,7 @@ export class CreateHarvestDto {
   dateEnd: string;
 }
 
-class DurationDto {
-  @ValidateNested()
-  @Type(() => QuantityValueDto)
-  value: QuantityValueDto;
-}
-
-/* ------------------------------
- * Update DTO (partial)
- * ------------------------------ */
-
-export class UpdateHarvestDto {
+export class UpdateActivityDto {
   @IsOptional()
   @IsString()
   name?: string;

+ 13 - 0
src/activity/activity.module.ts

@@ -0,0 +1,13 @@
+// src/harvest/harvest.module.ts
+import { Module } from '@nestjs/common';
+import { ActivityController } from './activity.controller';
+import { ActivityService } from './activity.service';
+import { ServiceModule } from 'src/services/service.module';
+
+@Module({
+  imports: [ServiceModule],
+  controllers: [ActivityController],
+  providers: [ActivityService],
+  exports: [ActivityService],
+})
+export class ActivityModule {}

+ 0 - 0
src/harvest/harvest.schema.ts → src/activity/activity.schema.ts


+ 124 - 0
src/activity/activity.service.ts

@@ -0,0 +1,124 @@
+// src/harvest/harvest.service.ts
+import { Injectable } from '@nestjs/common';
+import { HarvestActivity } from './activity.schema';
+import { CreateActivityDto, UpdateActivityDto } from './activity.dto';
+import { ObjectId } from 'mongodb';
+import { MongoService } from 'src/services/mongo.service';
+
+@Injectable()
+export class ActivityService {
+    private readonly collectionName = 'activities';
+
+    constructor(private readonly mongo: MongoService) { }
+
+    /* ------------------------------
+     * Utility: Convert DTO to Schema
+     * ------------------------------ */
+    private dtoToHarvestActivity(dto: CreateActivityDto): HarvestActivity {
+        return {
+            name: dto.name,
+            type: dto.type,
+            resources: dto.resources.map((r) => ({
+                ...r,
+                id: r.id ? new ObjectId(r.id) : new ObjectId(),
+            })),
+            duration: dto.duration,
+            outputs: dto.outputs.map((o) => ({
+                ...o,
+                id: o.id ? new ObjectId(o.id) : new ObjectId(),
+            })),
+            targets: dto.targets.map((t) => ({
+                ...t,
+                id: t.id ? new ObjectId(t.id) : new ObjectId(),
+            })),
+            dateStart: new Date(dto.dateStart),
+            dateEnd: new Date(dto.dateEnd),
+        };
+    }
+
+    /* ------------------------------
+     * CRUD OPERATIONS
+     * ------------------------------ */
+    async create(dto: CreateActivityDto) {
+        const activity = this.dtoToHarvestActivity(dto);
+        return this.mongo.createActivity(activity);
+    }
+
+    async findAll(filter: Record<string, any> = {}) {
+        return this.mongo.getAllActivities(filter);
+    }
+
+    async findById(id: string) {
+        return this.mongo.getActivityById(id);
+    }
+
+    async update(id: string, dto: UpdateActivityDto) {
+        // For partial updates, we only convert provided IDs
+        const update: any = { ...dto };
+        if (dto.duration) {
+            update.duration = {
+                value: {
+                    quantity: dto.duration.value?.quantity ?? 0,
+                    uom: dto.duration.value?.uom ?? 'hours',
+                },
+            };
+        }
+        if (dto.resources) {
+            update.resources = dto.resources.map((r) => ({
+                ...r,
+                id: r.id ? new ObjectId(r.id) : new ObjectId(),
+            }));
+        }
+        if (dto.outputs) {
+            update.outputs = dto.outputs.map((o) => ({
+                ...o,
+                id: o.id ? new ObjectId(o.id) : new ObjectId(),
+            }));
+        }
+        if (dto.targets) {
+            update.targets = dto.targets.map((t) => ({
+                ...t,
+                id: t.id ? new ObjectId(t.id) : new ObjectId(),
+            }));
+        }
+        if (dto.dateStart) update.dateStart = new Date(dto.dateStart);
+        if (dto.dateEnd) update.dateEnd = new Date(dto.dateEnd);
+
+        return this.mongo.updateActivity(id, update);
+    }
+
+    async delete(id: string) {
+        return this.mongo.deleteActivity(id);
+    }
+
+    /* ------------------------------
+     * Aggregations / Queries
+     * ------------------------------ */
+
+    async getTotalResourceHours() {
+        return this.mongo.getTotalResourceHours();
+    }
+
+    async getTotalOutputWeight() {
+        return this.mongo.getTotalOutputWeight();
+    }
+
+    async findByDateRange(start: Date, end: Date) {
+        return this.mongo.getActivitiesByDateRange(start, end);
+    }
+
+    /* ------------------------------
+   * Aggregations / Custom Queries
+   * ------------------------------ */
+
+    async getTotalWorkers(filter: { start?: Date; end?: Date; location?: string }) {
+        return this.mongo.getTotalWorkers(filter);
+    }
+
+    async getTotalOutputs(filter: { start?: Date; end?: Date }) {
+        return this.mongo.getTotalOutputs(filter);
+    }
+
+
+
+}

+ 2 - 2
src/app.module.ts

@@ -8,7 +8,7 @@ import { AppService } from './app.service';
 import { MongooseModule } from '@nestjs/mongoose';
 import { mongooseConfig } from './config/mongoose.config';
 import { ServiceModule } from './services/service.module';
-import { HarvestModule } from './harvest/harvest.module';
+import { ActivityModule } from './activity/activity.module';
 
 @Module({
   imports: [
@@ -20,7 +20,7 @@ import { HarvestModule } from './harvest/harvest.module';
     PaymentModule,
     PlantationTreeModule,
     ServiceModule,
-    HarvestModule
+    ActivityModule
   ],
   controllers: [AppController],
   providers: [AppService],

+ 0 - 49
src/harvest/harvest.controller.ts

@@ -1,49 +0,0 @@
-// src/harvest/harvest.controller.ts
-import { Controller, Get, Post, Body, Param, Put, Delete, Query } from '@nestjs/common';
-import { HarvestService } from './harvest.service';
-import { CreateHarvestDto, UpdateHarvestDto } from './harvest.dto';
-
-@Controller('harvest')
-export class HarvestController {
-  constructor(private readonly harvestService: HarvestService) {}
-
-  @Post()
-  async create(@Body() dto: CreateHarvestDto) {
-    return this.harvestService.create(dto);
-  }
-
-  @Get()
-  async findAll(@Query() query: Record<string, any>) {
-    return this.harvestService.findAll(query);
-  }
-
-  @Get(':id')
-  async findOne(@Param('id') id: string) {
-    return this.harvestService.findById(id);
-  }
-
-  @Put(':id')
-  async update(@Param('id') id: string, @Body() dto: UpdateHarvestDto) {
-    return this.harvestService.update(id, dto);
-  }
-
-  @Delete(':id')
-  async remove(@Param('id') id: string) {
-    return this.harvestService.delete(id);
-  }
-
-  @Get('stats/resources')
-  async totalResources() {
-    return this.harvestService.getTotalResourceHours();
-  }
-
-  @Get('stats/outputs')
-  async totalOutputs() {
-    return this.harvestService.getTotalOutputWeight();
-  }
-
-  @Get('date-range')
-  async byDateRange(@Query('start') start: string, @Query('end') end: string) {
-    return this.harvestService.findByDateRange(new Date(start), new Date(end));
-  }
-}

+ 0 - 13
src/harvest/harvest.module.ts

@@ -1,13 +0,0 @@
-// src/harvest/harvest.module.ts
-import { Module } from '@nestjs/common';
-import { HarvestController } from './harvest.controller';
-import { HarvestService } from './harvest.service';
-import { ServiceModule } from 'src/services/service.module';
-
-@Module({
-  imports: [ServiceModule],
-  controllers: [HarvestController],
-  providers: [HarvestService],
-  exports: [HarvestService],
-})
-export class HarvestModule {}

+ 0 - 101
src/harvest/harvest.service.ts

@@ -1,101 +0,0 @@
-// src/harvest/harvest.service.ts
-import { Injectable } from '@nestjs/common';
-import { HarvestActivity } from './harvest.schema';
-import { CreateHarvestDto, UpdateHarvestDto } from './harvest.dto';
-import { ObjectId } from 'mongodb';
-import { MongoService } from 'src/services/mongo.service';
-
-@Injectable()
-export class HarvestService {
-  private readonly collectionName = 'activities';
-
-  constructor(private readonly mongo: MongoService) {}
-
-  /* ------------------------------
-   * Utility: Convert DTO to Schema
-   * ------------------------------ */
-  private dtoToHarvestActivity(dto: CreateHarvestDto): HarvestActivity {
-    return {
-      name: dto.name,
-      type: dto.type,
-      resources: dto.resources.map((r) => ({
-        ...r,
-        id: r.id ? new ObjectId(r.id) : new ObjectId(),
-      })),
-      duration: dto.duration,
-      outputs: dto.outputs.map((o) => ({
-        ...o,
-        id: o.id ? new ObjectId(o.id) : new ObjectId(),
-      })),
-      targets: dto.targets.map((t) => ({
-        ...t,
-        id: t.id ? new ObjectId(t.id) : new ObjectId(),
-      })),
-      dateStart: new Date(dto.dateStart),
-      dateEnd: new Date(dto.dateEnd),
-    };
-  }
-
-  /* ------------------------------
-   * CRUD OPERATIONS
-   * ------------------------------ */
-  async create(dto: CreateHarvestDto) {
-    const activity = this.dtoToHarvestActivity(dto);
-    return this.mongo.createActivity(activity);
-  }
-
-  async findAll(filter: Record<string, any> = {}) {
-    return this.mongo.getAllActivities(filter);
-  }
-
-  async findById(id: string) {
-    return this.mongo.getActivityById(id);
-  }
-
-  async update(id: string, dto: UpdateHarvestDto) {
-    // For partial updates, we only convert provided IDs
-    const update: any = { ...dto };
-    if (dto.resources) {
-      update.resources = dto.resources.map((r) => ({
-        ...r,
-        id: r.id ? new ObjectId(r.id) : new ObjectId(),
-      }));
-    }
-    if (dto.outputs) {
-      update.outputs = dto.outputs.map((o) => ({
-        ...o,
-        id: o.id ? new ObjectId(o.id) : new ObjectId(),
-      }));
-    }
-    if (dto.targets) {
-      update.targets = dto.targets.map((t) => ({
-        ...t,
-        id: t.id ? new ObjectId(t.id) : new ObjectId(),
-      }));
-    }
-    if (dto.dateStart) update.dateStart = new Date(dto.dateStart);
-    if (dto.dateEnd) update.dateEnd = new Date(dto.dateEnd);
-
-    return this.mongo.updateActivity(id, update);
-  }
-
-  async delete(id: string) {
-    return this.mongo.deleteActivity(id);
-  }
-
-  /* ------------------------------
-   * Aggregations / Queries
-   * ------------------------------ */
-
-  async getTotalResourceHours() {
-    return this.mongo.getTotalResourceHours();
-  }
-
-  async getTotalOutputWeight() {
-    return this.mongo.getTotalOutputWeight();
-  }
-
-  async findByDateRange(start: Date, end: Date) {
-    return this.mongo.getActivitiesByDateRange(start, end);
-  }
-}

+ 166 - 98
src/services/mongo.service.ts

@@ -3,102 +3,170 @@ import { MongoClient, Db, Collection, ObjectId } from 'mongodb';
 
 @Injectable()
 export class MongoService implements OnModuleInit, OnModuleDestroy {
-  private client: MongoClient;
-  private db: Db;
-  private readonly logger = new Logger(MongoService.name);
-
-  async onModuleInit() {
-    const uri = process.env.MONGO_URI;
-    if (!uri) throw new Error('MONGO_URI not set in environment variables');
-
-    this.client = new MongoClient(uri);
-    await this.client.connect();
-
-    this.db = this.client.db('swopt-ai-test-a');
-    this.logger.log('Connected to MongoDB');
-  }
-
-  async onModuleDestroy() {
-    await this.client.close();
-    this.logger.log('MongoDB connection closed');
-  }
-
-  // Get collection reference
-  private collection(name: string): Collection {
-    return this.db.collection(name);
-  }
-
-  /* ------------------------------
-   * CRUD OPERATIONS (activities)
-   * ------------------------------ */
-
-  async createActivity(data: any) {
-    const result = await this.collection('activities').insertOne(data);
-    return { insertedId: result.insertedId };
-  }
-
-  async getAllActivities(filter: Record<string, any> = {}) {
-    return this.collection('activities').find(filter).toArray();
-  }
-
-  async getActivityById(id: string) {
-    return this.collection('activities').findOne({ _id: new ObjectId(id) });
-  }
-
-  async updateActivity(id: string, update: Record<string, any>) {
-    const result = await this.collection('activities').updateOne(
-      { _id: new ObjectId(id) },
-      { $set: update },
-    );
-    return { modifiedCount: result.modifiedCount };
-  }
-
-  async deleteActivity(id: string) {
-    const result = await this.collection('activities').deleteOne({ _id: new ObjectId(id) });
-    return { deletedCount: result.deletedCount };
-  }
-
-  /* ------------------------------
-   * AGGREGATION EXAMPLES
-   * ------------------------------ */
-
-  // Example 1: Get total hours worked per resource type
-  async getTotalResourceHours() {
-    return this.collection('activities')
-      .aggregate([
-        { $unwind: '$resources' },
-        {
-          $group: {
-            _id: '$resources.type',
-            totalHours: { $sum: { $toDecimal: '$resources.value.quantity' } },
-          },
-        },
-      ])
-      .toArray();
-  }
-
-  // Example 2: Get total outputs by weight (e.g., total FFB harvested)
-  async getTotalOutputWeight() {
-    return this.collection('activities')
-      .aggregate([
-        { $unwind: '$outputs' },
-        {
-          $group: {
-            _id: '$outputs.type',
-            totalWeightKg: { $sum: { $toDecimal: '$outputs.weightValue.weight' } },
-          },
-        },
-      ])
-      .toArray();
-  }
-
-  // Example 3: Activities within date range
-  async getActivitiesByDateRange(start: Date, end: Date) {
-    return this.collection('activities')
-      .find({
-        dateStart: { $gte: start },
-        dateEnd: { $lte: end },
-      })
-      .toArray();
-  }
+    private client: MongoClient;
+    private db: Db;
+    private readonly logger = new Logger(MongoService.name);
+
+    async onModuleInit() {
+        const uri = process.env.MONGO_URI;
+        if (!uri) throw new Error('MONGO_URI not set in environment variables');
+
+        this.client = new MongoClient(uri);
+        await this.client.connect();
+
+        this.db = this.client.db('swopt-ai-test-a');
+        this.logger.log('Connected to MongoDB');
+    }
+
+    async onModuleDestroy() {
+        await this.client.close();
+        this.logger.log('MongoDB connection closed');
+    }
+
+    // Get collection reference
+    private collection(name: string): Collection {
+        return this.db.collection(name);
+    }
+
+    /* ------------------------------
+     * CRUD OPERATIONS (activities)
+     * ------------------------------ */
+
+    async createActivity(data: any) {
+        const result = await this.collection('activities').insertOne(data);
+        return { insertedId: result.insertedId };
+    }
+
+    async getAllActivities(filter: Record<string, any> = {}) {
+        return this.collection('activities').find(filter).toArray();
+    }
+
+    async getActivityById(id: string) {
+        return this.collection('activities').findOne({ _id: new ObjectId(id) });
+    }
+
+    async updateActivity(id: string, update: Record<string, any>) {
+        const result = await this.collection('activities').updateOne(
+            { _id: new ObjectId(id) },
+            { $set: update },
+        );
+        return { modifiedCount: result.modifiedCount };
+    }
+
+    async deleteActivity(id: string) {
+        const result = await this.collection('activities').deleteOne({ _id: new ObjectId(id) });
+        return { deletedCount: result.deletedCount };
+    }
+
+    /* ------------------------------
+     * AGGREGATION EXAMPLES
+     * ------------------------------ */
+
+    // Example 1: Get total hours worked per resource type
+    async getTotalResourceHours() {
+        return this.collection('activities')
+            .aggregate([
+                { $unwind: '$resources' },
+                {
+                    $group: {
+                        _id: '$resources.type',
+                        totalHours: { $sum: { $toDecimal: '$resources.value.quantity' } },
+                    },
+                },
+            ])
+            .toArray();
+    }
+
+    // Example 2: Get total outputs by weight (e.g., total FFB harvested)
+    async getTotalOutputWeight() {
+        return this.collection('activities')
+            .aggregate([
+                { $unwind: '$outputs' },
+                {
+                    $group: {
+                        _id: '$outputs.type',
+                        totalWeightKg: { $sum: { $toDecimal: '$outputs.weightValue.weight' } },
+                    },
+                },
+            ])
+            .toArray();
+    }
+
+    // Example 3: Activities within date range
+    async getActivitiesByDateRange(start: Date, end: Date) {
+        return this.collection('activities')
+            .find({
+                dateStart: { $gte: start },
+                dateEnd: { $lte: end },
+            })
+            .toArray();
+    }
+
+
+    // 1️⃣ Total workers (optionally filter by date range or location)
+    async getTotalWorkers(filter: { start?: Date; end?: Date; location?: string } = {}) {
+        const matchStage: any = {};
+
+        if (filter.start && filter.end) {
+            matchStage.dateStart = { $gte: filter.start };
+            matchStage.dateEnd = { $lte: filter.end };
+        }
+
+        if (filter.location) {
+            matchStage['targets.name'] = filter.location;
+        }
+
+        const pipeline = [
+            { $match: Object.keys(matchStage).length ? matchStage : {} },
+            { $unwind: '$resources' },
+            { $match: { 'resources.type': 'worker' } },
+            {
+                $group: {
+                    _id: '$resources.name',
+                    totalHours: { $sum: { $toDecimal: '$resources.value.quantity' } },
+                },
+            },
+            {
+                $project: {
+                    _id: 0,
+                    worker: '$_id',
+                    totalHours: 1,
+                },
+            },
+        ];
+
+        return this.collection('activities').aggregate(pipeline).toArray();
+    }
+
+    // 2️⃣ Total outputs (bunch + weight, with optional date range)
+    async getTotalOutputs(filter: { start?: Date; end?: Date } = {}) {
+        const matchStage: any = {};
+
+        if (filter.start && filter.end) {
+            matchStage.dateStart = { $gte: filter.start };
+            matchStage.dateEnd = { $lte: filter.end };
+        }
+
+        const pipeline = [
+            { $match: Object.keys(matchStage).length ? matchStage : {} },
+            { $unwind: '$outputs' },
+            {
+                $group: {
+                    _id: '$outputs.type',
+                    totalBunch: { $sum: { $toDecimal: '$outputs.value.quantity' } },
+                    totalWeightKg: { $sum: { $toDecimal: '$outputs.weightValue.weight' } },
+                },
+            },
+            {
+                $project: {
+                    _id: 0,
+                    outputType: '$_id',
+                    totalBunch: 1,
+                    totalWeightKg: 1,
+                },
+            },
+        ];
+
+        return this.collection('activities').aggregate(pipeline).toArray();
+    }
 }