All files / src/domains/hr/attendance/policy attendance-policy.service.ts

0% Statements 0/40
0% Branches 0/23
0% Functions 0/8
0% Lines 0/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163                                                                                                                                                                                                                                                                                                                                     
import {
  Injectable,
  NotFoundException,
  ConflictException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import {
  AttendancePolicy,
  AttendancePolicyAssignment,
  AttendancePolicyVersion,
} from '../schemas';
import {
  CreateAttendancePolicyDto,
  UpdateAttendancePolicyDto,
  AssignPolicyDto,
} from './dto/attendance-policy.dto';
 
@Injectable()
export class AttendancePolicyService {
  constructor(
    @InjectModel(AttendancePolicy.name)
    private policyModel: Model<AttendancePolicy>,
    @InjectModel(AttendancePolicyAssignment.name)
    private assignmentModel: Model<AttendancePolicyAssignment>,
    @InjectModel(AttendancePolicyVersion.name)
    private versionModel: Model<AttendancePolicyVersion>,
  ) {}
 
  async create(
    tenantId: string,
    createDto: CreateAttendancePolicyDto,
    userId: string,
  ) {
    const existing = await this.policyModel.findOne({
      tenantId,
      policyCode: createDto.policyCode,
    });
    if (existing) {
      throw new ConflictException(
        `Policy with code ${createDto.policyCode} already exists`,
      );
    }
 
    const created = new this.policyModel({
      ...createDto,
      tenantId,
      createdBy: userId,
    });
 
    const saved = await created.save();
 
    await this.versionModel.create({
      tenantId,
      policyId: saved._id,
      policyData: saved.toObject(),
      effectiveFrom: saved.effectiveFrom,
      createdBy: userId,
    });
 
    return saved;
  }
 
  async findAll(tenantId: string) {
    return this.policyModel.find({ tenantId }).exec();
  }
 
  async findOne(tenantId: string, id: string) {
    const policy = await this.policyModel.findOne({ _id: id, tenantId }).exec();
    if (!policy) {
      throw new NotFoundException(`Attendance Policy #${id} not found`);
    }
    return policy;
  }
 
  async update(
    tenantId: string,
    id: string,
    updateDto: UpdateAttendancePolicyDto,
    userId: string,
  ) {
    const policy = await this.findOne(tenantId, id);
 
    // Instead of directly updating, create a new version if it alters core logic
    const updated = await this.policyModel.findOneAndUpdate(
      { _id: id, tenantId },
      { $set: updateDto },
      { new: true },
    );
 
    if (!updated) {
      throw new NotFoundException(`Attendance Policy #${id} not found`);
    }
 
    await this.versionModel.create({
      tenantId,
      policyId: updated._id,
      policyData: updated.toObject(),
      effectiveFrom: updated.effectiveFrom,
      createdBy: userId,
    });
 
    return updated;
  }
 
  async assignPolicy(tenantId: string, id: string, assignDto: AssignPolicyDto) {
    await this.findOne(tenantId, id); // Ensure policy exists
 
    // Deactivate current active assignments for this target if necessary, or just upsert
    // For simplicity, we just create the new assignment record. Real logic might need overlap checking.
    const created = new this.assignmentModel({
      ...assignDto,
      tenantId,
      policyId: id,
    });
    return created.save();
  }
 
  async getAssignments(tenantId: string, id: string) {
    return this.assignmentModel.find({ tenantId, policyId: id }).exec();
  }
 
  async resolvePolicyForEmployee(
    tenantId: string,
    employeeId: string,
    criteria: {
      branchId?: string;
      departmentId?: string;
      designationId?: string;
      teamId?: string;
    } = {},
  ) {
    // Basic resolution hierarchy: Employee > Team > Designation > Department > Branch > Tenant
    const assignmentTypes = [
      { type: 'Employee', targetId: employeeId },
      { type: 'Team', targetId: criteria.teamId },
      { type: 'Designation', targetId: criteria.designationId },
      { type: 'Department', targetId: criteria.departmentId },
      { type: 'Branch', targetId: criteria.branchId },
      { type: 'Tenant', targetId: tenantId },
    ];
 
    for (const rule of assignmentTypes) {
      if (!rule.targetId) continue;
 
      const assignment = await this.assignmentModel
        .findOne({
          tenantId,
          assignmentType: rule.type,
          assignmentTargetId: rule.targetId,
        })
        .sort({ createdAt: -1 });
 
      if (assignment) {
        return this.policyModel
          .findOne({ _id: assignment.policyId, tenantId })
          .exec();
      }
    }
    return null; // or default policy
  }
}