|
|
@@ -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();
|
|
|
+ }
|
|
|
}
|