Untitled

 avatar
unknown
plain_text
a year ago
6.9 kB
11
Indexable
"use strict";

const httpStatus = require("http-status");
const ApiError = require("../../utils/ApiError");
const ExcelJS = require("exceljs");

const RoadSourceBottomUpPollutantModel = require("../models/roadSourceBottomUpPollutant.model");

class RoadSourceBottomUpPollutantService {
    /**
     * Service: Get list of Road Source Bottom-Up Pollutants with pagination
     *
     * @version: 1.0
     * @param {Object} params
     * @param {number} params.page
     * @param {number} params.limit
     * @returns {Promise<Object>}
     */
    async getListRoadSourceBottomUpPollutant({ page, limit }) {
        const skip = (page - 1) * limit;
        const totalRecords = await RoadSourceBottomUpPollutantModel.countDocuments();
        const totalPages = Math.ceil(totalRecords / limit);

        const records = await RoadSourceBottomUpPollutantModel.find({})
            .skip(skip)
            .limit(limit)
            .sort({ createdAt: -1 })
            .lean();

        const data = records.map((r) => ({
            _id: r._id,
            name: r.name,
            createdAt: r.createdAt,
            updatedAt: r.updatedAt,
        }));

        return { currentPage: page, totalPages, totalRecords, limit, data };
    }

    /**
     * Service: Create a new pollutant
     *
     * @version: 1.0
     * @param {{ name: string }} payload
     * @returns {Promise<Object>}
     */
    async createRoadSourceBottomUpPollutant(payload) {
        const name = (payload.name || "").trim();
        if (!name) {
            throw new ApiError(httpStatus.BAD_REQUEST, "Name is required.");
        }

        const existing = await RoadSourceBottomUpPollutantModel.findOne({
            name: new RegExp(`^${name}$`, "i"),
        }).lean();
        if (existing) {
            throw new ApiError(httpStatus.CONFLICT, "Pollutant already exists.");
        }

        const doc = await RoadSourceBottomUpPollutantModel.create({ name });
        return {
            _id: doc._id,
            name: doc.name,
            createdAt: doc.createdAt,
            updatedAt: doc.updatedAt,
        };
    }

    /**
     * Service: Get pollutant by ID
     *
     * @version: 1.0
     * @param {string} id
     * @returns {Promise<Object>}
     */
    async getRoadSourceBottomUpPollutantById(id) {
        const record = await RoadSourceBottomUpPollutantModel.findById(id).lean();
        if (!record) {
            throw new ApiError(httpStatus.NOT_FOUND, "Pollutant not found.");
        }
        return {
            _id: record._id,
            name: record.name,
            createdAt: record.createdAt,
            updatedAt: record.updatedAt,
        };
    }

    /**
     * Service: Update pollutant name by ID
     *
     * @version: 1.0
     * @param {string} id
     * @param {string} name
     * @returns {Promise<Object>}
     */
    async updateRoadSourceBottomUpPollutantById(id, name) {
        const newName = (name || "").trim();
        if (!newName) {
            throw new ApiError(httpStatus.BAD_REQUEST, "Name is required.");
        }

        const record = await RoadSourceBottomUpPollutantModel.findById(id).lean();
        if (!record) {
            throw new ApiError(httpStatus.NOT_FOUND, "Pollutant not found.");
        }

        const clash = await RoadSourceBottomUpPollutantModel.findOne({
            _id: { $ne: id },
            name: new RegExp(`^${newName}$`, "i"),
        }).lean();
        if (clash) {
            throw new ApiError(httpStatus.CONFLICT, "Another pollutant with this name already exists.");
        }

        const updated = await RoadSourceBottomUpPollutantModel.findByIdAndUpdate(
            id,
            { name: newName },
            { new: true, runValidators: true }
        ).lean();

        return {
            _id: updated._id,
            name: updated.name,
            createdAt: updated.createdAt,
            updatedAt: updated.updatedAt,
        };
    }

    /**
     * Service: Delete pollutant by ID
     *
     * @version: 1.0
     * @param {string} id
     * @returns {Promise<void>}
     */
    async deleteRoadSourceBottomUpPollutantById(id) {
        const deleted = await RoadSourceBottomUpPollutantModel.findByIdAndDelete(id);
        if (!deleted) {
            throw new ApiError(httpStatus.NOT_FOUND, "Pollutant not found.");
        }
    }

    /**
     * Service: Import pollutants from Excel
     * - Read sheet 1
     * - Column A = name
     * - Skip header (start from row 2)
     * - Stop at first completely empty row
     * - Case-insensitive dedupe; unordered bulk upsert
     *
     * @version: 1.0
     * @param {object} file - The uploaded Excel file (with `buffer`)
     * @returns {Promise<{added:number, skipped:number, processed:number}>}
     */
    async importRoadSourceBottomUpPollutant(file) {
        const workbook = new ExcelJS.Workbook();
        await workbook.xlsx.load(file.buffer);
        const worksheet = workbook.getWorksheet(1);

        if (!worksheet) {
            throw new ApiError(httpStatus.BAD_REQUEST, "Invalid Excel file: No worksheet found.");
        }

        const existing = await RoadSourceBottomUpPollutantModel.find()
            .select("_id name")
            .lean();

        const existingSet = new Set(
            existing.map((d) => String(d.name || "").trim().toLowerCase())
        );

        const operations = [];
        const batchSize = 200;

        let added = 0;
        let skipped = 0;
        let processed = 0;

        for (let i = 2; i <= worksheet.rowCount; i++) {
            const row = worksheet.getRow(i);
            const nameRaw = row.getCell(1).value; // column A

            const isEmpty = nameRaw == null || String(nameRaw).trim() === "";
            if (isEmpty) break;

            processed++;

            const name = String(nameRaw).trim();
            const key = name.toLowerCase();

            if (!name) {
                skipped++;
                continue;
            }

            if (existingSet.has(key)) {
                skipped++;
                continue;
            }

            operations.push({
                updateOne: {
                    filter: { name: new RegExp(`^${name}$`, "i") },
                    update: { $setOnInsert: { name } },
                    upsert: true,
                },
            });

            existingSet.add(key);
            added++;

            if (operations.length >= batchSize) {
                await RoadSourceBottomUpPollutantModel.bulkWrite(operations, { ordered: false });
                operations.length = 0;
            }
        }

        if (operations.length > 0) {
            await RoadSourceBottomUpPollutantModel.bulkWrite(operations, { ordered: false });
        }

        return { added, skipped, processed };
    }
}

module.exports = new RoadSourceBottomUpPollutantService();
Editor is loading...
Leave a Comment