dt-common-device
Version: 
A secure and robust device management library for IoT applications
643 lines (642 loc) • 29.4 kB
JavaScript
;
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
    if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
    return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IssueService = void 0;
const typedi_1 = require("typedi");
const Issue_model_1 = require("../models/Issue.model");
const issue_types_1 = require("../../../types/issue.types");
const IssueBuilder_1 = require("../entities/IssueBuilder");
let IssueService = (() => {
    let _classDecorators = [(0, typedi_1.Service)()];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    var IssueService = _classThis = class {
        constructor(issueRepository) {
            this.issueRepository = issueRepository;
        }
        /**
         * Create a readiness issue using IssueBuilder
         */
        async createReadinessIssue(propertyId, title, description, createdBy, entityId, entityType, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createReadinessIssue()
                .setPropertyId(propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (entityId)
                issueBuilder.setEntityId(entityId);
            if (entityType)
                issueBuilder.setEntityType(entityType);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create an operations issue using IssueBuilder
         */
        async createOperationsIssue(propertyId, title, description, createdBy, entityId, entityType, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createOperationsIssue()
                .setPropertyId(propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (entityId)
                issueBuilder.setEntityId(entityId);
            if (entityType)
                issueBuilder.setEntityType(entityType);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create a security issue using IssueBuilder
         */
        async createSecurityIssue(propertyId, title, description, createdBy, entityId, entityType, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createSecurityIssue()
                .setPropertyId(propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (entityId)
                issueBuilder.setEntityId(entityId);
            if (entityType)
                issueBuilder.setEntityType(entityType);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create an energy issue using IssueBuilder
         */
        async createEnergyIssue(propertyId, title, description, createdBy, entityId, entityType, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createEnergyIssue()
                .setPropertyId(propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (entityId)
                issueBuilder.setEntityId(entityId);
            if (entityType)
                issueBuilder.setEntityType(entityType);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create a device-specific issue using IssueBuilder
         */
        async createDeviceIssue(deviceId, propertyId, title, description, createdBy, category, priority, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createDeviceIssue(deviceId, propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (category)
                issueBuilder.setCategory(category);
            if (priority)
                issueBuilder.setPriority(priority);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create a hub-specific issue using IssueBuilder
         */
        async createHubIssue(hubId, propertyId, title, description, createdBy, category, priority, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createHubIssue(hubId, propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (category)
                issueBuilder.setCategory(category);
            if (priority)
                issueBuilder.setPriority(priority);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create a user-specific issue using IssueBuilder
         */
        async createUserIssue(userId, propertyId, title, description, createdBy, category, priority, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createUserIssue(userId, propertyId)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (category)
                issueBuilder.setCategory(category);
            if (priority)
                issueBuilder.setPriority(priority);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create a maintenance issue using IssueBuilder
         */
        async createMaintenanceIssue(propertyId, title, description, createdBy, entityId, entityType, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createMaintenanceIssue(propertyId, entityId, entityType)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create an urgent issue using IssueBuilder
         */
        async createUrgentIssue(propertyId, title, description, createdBy, entityId, entityType, assignedTo, dueDate) {
            const issueBuilder = IssueBuilder_1.IssueBuilder.createUrgentIssue(propertyId, entityId, entityType)
                .setTitle(title)
                .setDescription(description)
                .setCreatedBy(createdBy);
            if (assignedTo)
                issueBuilder.setAssignedTo(assignedTo);
            if (dueDate)
                issueBuilder.setDueDate(dueDate);
            return await this.createIssue(issueBuilder);
        }
        /**
         * Create a new issue with business logic validation
         * Accepts either a CreateIssueData object or an IssueBuilder instance
         */
        async createIssue(issueData) {
            let processedIssueData;
            // Handle IssueBuilder instance
            if (issueData instanceof IssueBuilder_1.IssueBuilder) {
                processedIssueData = issueData.build();
            }
            else {
                processedIssueData = issueData;
            }
            // Business logic: Validate issue data
            this.validateIssueData(processedIssueData);
            // Business logic: Set default priority if not provided
            if (!processedIssueData.priority) {
                processedIssueData.priority = this.determineDefaultPriority(processedIssueData.category);
            }
            // Business logic: Validate due date is in the future
            if (processedIssueData.dueDate && processedIssueData.dueDate <= new Date()) {
                throw new Error("Due date must be in the future");
            }
            return await this.issueRepository.create(processedIssueData);
        }
        /**
         * Get issue by ID with business logic
         */
        async getIssueById(id, includeDeleted = false) {
            if (!id) {
                throw new Error("Issue ID is required");
            }
            const issue = await this.issueRepository.findById(id, includeDeleted);
            // Business logic: Check if issue is overdue
            if (issue?.dueDate &&
                issue.dueDate < new Date() &&
                issue.status !== issue_types_1.IssueStatus.RESOLVED) {
                // You could add a flag or handle overdue logic here
                console.warn(`Issue ${id} is overdue`);
            }
            return issue;
        }
        /**
         * Get all issues with business logic filtering
         */
        async getIssues(filters = {}) {
            // Business logic: Validate filters
            this.validateFilters(filters);
            // Business logic: Apply business rules to filters
            const enhancedFilters = this.applyBusinessRules(filters);
            return await this.issueRepository.findAll(enhancedFilters);
        }
        /**
         * Update an issue with business logic validation
         */
        async updateIssue(id, updateData) {
            if (!id) {
                throw new Error("Issue ID is required");
            }
            // Business logic: Validate update data
            this.validateUpdateData(updateData);
            // Business logic: Check if issue exists and is not deleted
            const existingIssue = await this.issueRepository.findById(id);
            if (!existingIssue) {
                throw new Error("Issue not found");
            }
            // Business logic: Handle status transitions
            if (updateData.status) {
                this.validateStatusTransition(existingIssue.status, updateData.status);
            }
            // Business logic: Handle priority changes
            if (updateData.priority) {
                this.validatePriorityChange(existingIssue.priority, updateData.priority);
            }
            return await this.issueRepository.update(id, updateData);
        }
        /**
         * Soft delete an issue with business logic
         */
        async deleteIssue(id, deletedBy) {
            if (!id || !deletedBy) {
                throw new Error("Issue ID and deleted by user are required");
            }
            // Business logic: Check if issue can be deleted
            const issue = await this.issueRepository.findById(id);
            if (!issue) {
                throw new Error("Issue not found");
            }
            // Business logic: Prevent deletion of resolved issues (optional rule)
            if (issue.status === issue_types_1.IssueStatus.RESOLVED) {
                throw new Error("Cannot delete resolved issues");
            }
            return await this.issueRepository.softDelete(id, deletedBy);
        }
        /**
         * Permanently delete an issue
         */
        async permanentlyDeleteIssue(id) {
            return await this.issueRepository.hardDelete(id);
        }
        /**
         * Add a comment with business logic
         */
        async addComment(issueId, commentData) {
            if (!issueId || !commentData.userId || !commentData.content) {
                throw new Error("Issue ID, user ID, and comment content are required");
            }
            // Business logic: Check if issue exists and is active
            const issue = await this.issueRepository.findById(issueId);
            if (!issue) {
                throw new Error("Issue not found");
            }
            if (issue.isDeleted) {
                throw new Error("Cannot add comment to deleted issue");
            }
            // Business logic: Update issue status based on comment (optional)
            const shouldUpdateStatus = this.shouldUpdateStatusOnComment(commentData.content);
            if (shouldUpdateStatus && issue.status === issue_types_1.IssueStatus.PENDING) {
                await this.issueRepository.update(issueId, {
                    status: issue_types_1.IssueStatus.IN_PROGRESS,
                    updatedBy: commentData.userId,
                });
            }
            // Add comment using the model instance methods
            const issueModel = await Issue_model_1.IssueModel.findById(issueId);
            if (issueModel) {
                issueModel.addComment(commentData);
                return await issueModel.save();
            }
            return null;
        }
        /**
         * Update a comment on an issue
         */
        async updateComment(issueId, commentId, content, userId) {
            const issueModel = await Issue_model_1.IssueModel.findById(issueId);
            if (!issueModel)
                return false;
            const success = issueModel.updateComment(commentId, content, userId);
            if (success) {
                await issueModel.save();
            }
            return success;
        }
        /**
         * Remove a comment from an issue
         */
        async removeComment(issueId, commentId) {
            const issueModel = await Issue_model_1.IssueModel.findById(issueId);
            if (!issueModel)
                return false;
            const success = issueModel.removeComment(commentId);
            if (success) {
                await issueModel.save();
            }
            return success;
        }
        /**
         * Resolve an issue with business logic
         */
        async resolveIssue(id, resolvedBy) {
            if (!id || !resolvedBy) {
                throw new Error("Issue ID and resolved by user are required");
            }
            // Business logic: Check if issue can be resolved
            const issue = await this.issueRepository.findById(id);
            if (!issue) {
                throw new Error("Issue not found");
            }
            if (issue.status === issue_types_1.IssueStatus.RESOLVED) {
                throw new Error("Issue is already resolved");
            }
            if (issue.status === issue_types_1.IssueStatus.CANCELLED) {
                throw new Error("Cannot resolve cancelled issue");
            }
            // Business logic: Auto-assign if not assigned
            if (!issue.assignedTo) {
                await this.issueRepository.update(id, {
                    assignedTo: resolvedBy,
                    updatedBy: resolvedBy,
                });
            }
            // Resolve the issue using model instance methods
            const issueModel = await Issue_model_1.IssueModel.findById(id);
            if (issueModel) {
                issueModel.resolve(resolvedBy);
                return await issueModel.save();
            }
            return null;
        }
        /**
         * Reopen a resolved issue
         */
        async reopenIssue(id, reopenedBy) {
            const issueModel = await Issue_model_1.IssueModel.findById(id);
            if (!issueModel)
                return null;
            issueModel.reopen(reopenedBy);
            return await issueModel.save();
        }
        /**
         * Assign an issue with business logic
         */
        async assignIssue(id, userId, assignedBy) {
            if (!id || !userId || !assignedBy) {
                throw new Error("Issue ID, assignee user ID, and assigned by user are required");
            }
            // Business logic: Check if issue can be assigned
            const issue = await this.issueRepository.findById(id);
            if (!issue) {
                throw new Error("Issue not found");
            }
            if (issue.status === issue_types_1.IssueStatus.RESOLVED ||
                issue.status === issue_types_1.IssueStatus.CLOSED) {
                throw new Error("Cannot assign resolved or closed issue");
            }
            // Business logic: Update status to IN_PROGRESS when assigned
            const updateData = {
                assignedTo: userId,
                updatedBy: assignedBy,
            };
            if (issue.status === issue_types_1.IssueStatus.PENDING) {
                updateData.status = issue_types_1.IssueStatus.IN_PROGRESS;
            }
            return await this.issueRepository.update(id, updateData);
        }
        /**
         * Unassign an issue
         */
        async unassignIssue(id, unassignedBy) {
            const issueModel = await Issue_model_1.IssueModel.findById(id);
            if (!issueModel)
                return null;
            issueModel.unassign(unassignedBy);
            return await issueModel.save();
        }
        /**
         * Get issues by property with business logic
         */
        async getIssuesByProperty(propertyId, includeDeleted = false) {
            if (!propertyId) {
                throw new Error("Property ID is required");
            }
            return await this.issueRepository.findByProperty(propertyId, includeDeleted);
        }
        /**
         * Get issues assigned to a user with business logic
         */
        async getIssuesByAssignee(assignedTo, includeDeleted = false) {
            if (!assignedTo) {
                throw new Error("Assignee user ID is required");
            }
            return await this.issueRepository.findByAssignee(assignedTo, includeDeleted);
        }
        /**
         * Get issues by entity
         */
        async getIssuesByEntity(entityId, entityType, includeDeleted = false) {
            return await this.issueRepository.findByEntity(entityId, entityType, includeDeleted);
        }
        /**
         * Get issues by status
         */
        async getIssuesByStatus(status, includeDeleted = false) {
            return await this.issueRepository.findByStatus(status, includeDeleted);
        }
        /**
         * Get issues by priority
         */
        async getIssuesByPriority(priority, includeDeleted = false) {
            return await this.issueRepository.findByPriority(priority, includeDeleted);
        }
        /**
         * Get overdue issues with business logic
         */
        async getOverdueIssues(includeDeleted = false) {
            const overdueIssues = await this.issueRepository.findOverdue(includeDeleted);
            // Business logic: Log overdue issues for monitoring
            if (overdueIssues.length > 0) {
                console.warn(`Found ${overdueIssues.length} overdue issues`);
            }
            return overdueIssues;
        }
        /**
         * Get upcoming issues (due within specified days)
         */
        async getUpcomingIssues(days = 7, includeDeleted = false) {
            return await this.issueRepository.findUpcoming(days, includeDeleted);
        }
        /**
         * Get issue statistics with business logic
         */
        async getIssueStatistics(propertyId) {
            const stats = await this.issueRepository.getStatistics(propertyId);
            // Business logic: Calculate additional metrics
            const responseTime = this.calculateAverageResponseTime(stats);
            const resolutionRate = this.calculateResolutionRate(stats);
            // Log resolution rate for monitoring
            console.log(`Resolution rate: ${resolutionRate.toFixed(2)}%`);
            console.log(`Response time: ${responseTime.toFixed(2)} days`);
            // Business logic: Add alerts for critical metrics
            if (stats.overdue > 0) {
                console.warn(`Alert: ${stats.overdue} overdue issues detected`);
            }
            if (stats.byPriority[issue_types_1.IssuePriority.CRITICAL] > 0) {
                console.error(`Alert: ${stats.byPriority[issue_types_1.IssuePriority.CRITICAL]} critical issues require immediate attention`);
            }
            return stats;
        }
        /**
         * Search issues with business logic
         */
        async searchIssues(searchTerm, filters = {}) {
            if (!searchTerm || searchTerm.trim().length < 2) {
                throw new Error("Search term must be at least 2 characters long");
            }
            return await this.issueRepository.search(searchTerm, filters);
        }
        // Private business logic methods
        validateIssueData(data) {
            if (!data.title || data.title.trim().length < 5) {
                throw new Error("Issue title must be at least 5 characters long");
            }
            if (!data.description || data.description.trim().length < 10) {
                throw new Error("Issue description must be at least 10 characters long");
            }
            if (!data.propertyId) {
                throw new Error("Property ID is required");
            }
            if (!data.createdBy) {
                throw new Error("Created by user ID is required");
            }
        }
        validateFilters(filters) {
            if (filters.limit && (filters.limit < 1 || filters.limit > 100)) {
                throw new Error("Limit must be between 1 and 100");
            }
            if (filters.skip && filters.skip < 0) {
                throw new Error("Skip must be non-negative");
            }
        }
        validateUpdateData(data) {
            if (data.title && data.title.trim().length < 5) {
                throw new Error("Issue title must be at least 5 characters long");
            }
            if (data.description && data.description.trim().length < 10) {
                throw new Error("Issue description must be at least 10 characters long");
            }
        }
        validateStatusTransition(currentStatus, newStatus) {
            const validTransitions = {
                [issue_types_1.IssueStatus.PENDING]: [
                    issue_types_1.IssueStatus.IN_PROGRESS,
                    issue_types_1.IssueStatus.CANCELLED,
                    issue_types_1.IssueStatus.ON_HOLD,
                ],
                [issue_types_1.IssueStatus.IN_PROGRESS]: [
                    issue_types_1.IssueStatus.RESOLVED,
                    issue_types_1.IssueStatus.CANCELLED,
                    issue_types_1.IssueStatus.ON_HOLD,
                ],
                [issue_types_1.IssueStatus.RESOLVED]: [issue_types_1.IssueStatus.CLOSED, issue_types_1.IssueStatus.PENDING], // Reopen
                [issue_types_1.IssueStatus.CLOSED]: [issue_types_1.IssueStatus.PENDING], // Reopen
                [issue_types_1.IssueStatus.CANCELLED]: [issue_types_1.IssueStatus.PENDING], // Reopen
                [issue_types_1.IssueStatus.ON_HOLD]: [issue_types_1.IssueStatus.PENDING, issue_types_1.IssueStatus.IN_PROGRESS],
            };
            if (!validTransitions[currentStatus]?.includes(newStatus)) {
                throw new Error(`Invalid status transition from ${currentStatus} to ${newStatus}`);
            }
        }
        validatePriorityChange(currentPriority, newPriority) {
            // Business rule: Only allow priority escalation, not de-escalation for critical issues
            if (currentPriority === issue_types_1.IssuePriority.CRITICAL &&
                newPriority !== issue_types_1.IssuePriority.CRITICAL) {
                throw new Error("Cannot de-escalate priority of critical issues");
            }
        }
        determineDefaultPriority(category) {
            // Business logic: Determine default priority based on category
            const categoryPriorities = {
                [issue_types_1.IssuesCategory.READINESS]: issue_types_1.IssuePriority.MEDIUM,
                [issue_types_1.IssuesCategory.OPERATIONS]: issue_types_1.IssuePriority.HIGH,
                [issue_types_1.IssuesCategory.SECURITY]: issue_types_1.IssuePriority.CRITICAL,
                [issue_types_1.IssuesCategory.ENERGY]: issue_types_1.IssuePriority.LOW,
                [issue_types_1.IssuesCategory.OTHER]: issue_types_1.IssuePriority.MEDIUM,
            };
            return categoryPriorities[category] || issue_types_1.IssuePriority.MEDIUM;
        }
        applyBusinessRules(filters) {
            // Business logic: Apply additional filters based on business rules
            const enhancedFilters = { ...filters };
            // Example: Always exclude cancelled issues unless explicitly requested
            if (!enhancedFilters.status ||
                enhancedFilters.status !== issue_types_1.IssueStatus.CANCELLED) {
                enhancedFilters.status = { $ne: issue_types_1.IssueStatus.CANCELLED };
            }
            return enhancedFilters;
        }
        shouldUpdateStatusOnComment(content) {
            // Business logic: Determine if comment should trigger status change
            const statusKeywords = [
                "working on",
                "investigating",
                "fixing",
                "resolving",
            ];
            return statusKeywords.some((keyword) => content.toLowerCase().includes(keyword));
        }
        calculateAverageResponseTime(stats) {
            // Business logic: Calculate average response time (placeholder)
            return 0;
        }
        calculateResolutionRate(stats) {
            // Business logic: Calculate resolution rate
            if (stats.total === 0)
                return 0;
            return ((stats.resolved + stats.closed) / stats.total) * 100;
        }
    };
    __setFunctionName(_classThis, "IssueService");
    (() => {
        const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
        __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
        IssueService = _classThis = _classDescriptor.value;
        if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        __runInitializers(_classThis, _classExtraInitializers);
    })();
    return IssueService = _classThis;
})();
exports.IssueService = IssueService;