All files / src/domains/hr/leave/period leave-period.service.ts

0% Statements 0/47
0% Branches 0/24
0% Functions 0/11
0% Lines 0/43

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                                                                                                                                                                                                                                                                                             
import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import {
  LeavePeriod,
  LeavePeriodSegment,
  EmployeeLeavePeriodAssignment,
} from '../schemas';
 
@Injectable()
export class LeavePeriodService {
  constructor(
    @InjectModel(LeavePeriod.name) private periodModel: Model<LeavePeriod>,
    @InjectModel(LeavePeriodSegment.name)
    private segmentModel: Model<LeavePeriodSegment>,
    @InjectModel(EmployeeLeavePeriodAssignment.name)
    private empAssignModel: Model<EmployeeLeavePeriodAssignment>,
  ) {}
 
  async create(tenantId: string, dto: any, userId: string) {
    const period = await this.periodModel.create({
      ...dto,
      tenantId,
      createdBy: userId,
      status: 'draft',
    });
    // Auto-generate monthly segments
    await this.generateSegments(
      tenantId,
      period._id.toString(),
      dto.startDate,
      dto.endDate,
    );
    return period;
  }
 
  private async generateSegments(
    tenantId: string,
    periodId: string,
    startDate: string,
    endDate: string,
  ) {
    const segments = [];
    const start = new Date(startDate);
    const end = new Date(endDate);
    const current = new Date(start);
 
    while (current <= end) {
      const segStart = `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, '0')}-01`;
      const segEnd = new Date(current.getFullYear(), current.getMonth() + 1, 0)
        .toISOString()
        .split('T')[0];
      segments.push({
        tenantId,
        periodId,
        segmentName: current.toLocaleString('default', {
          month: 'long',
          year: 'numeric',
        }),
        startDate: segStart,
        endDate: segEnd > endDate ? endDate : segEnd,
        segmentType: 'Monthly',
      });
      current.setMonth(current.getMonth() + 1);
    }
    await this.segmentModel.insertMany(segments);
  }
 
  async findAll(tenantId: string, status?: string) {
    const filter: any = { tenantId };
    if (status) filter.status = status;
    return this.periodModel.find(filter).sort({ startDate: -1 }).exec();
  }
 
  async findOne(tenantId: string, id: string) {
    const doc = await this.periodModel.findOne({ _id: id, tenantId }).exec();
    if (!doc) throw new NotFoundException('Leave period not found');
    return doc;
  }
 
  async update(tenantId: string, id: string, dto: any) {
    const doc = await this.findOne(tenantId, id);
    if (doc.status === 'closed')
      throw new BadRequestException('Closed periods cannot be edited');
    Object.assign(doc, dto);
    return doc.save();
  }
 
  async close(tenantId: string, id: string, userId: string) {
    const doc = await this.findOne(tenantId, id);
    if (doc.status === 'closed')
      throw new BadRequestException('Period already closed');
    doc.status = 'closed';
    doc.closedAt = new Date();
    doc.closedBy = userId;
    return doc.save();
  }
 
  async reopen(tenantId: string, id: string) {
    return this.periodModel.findOneAndUpdate(
      { _id: id, tenantId },
      { $set: { status: 'active', closedAt: null } },
      { new: true },
    );
  }
 
  async getSegments(tenantId: string, periodId: string) {
    return this.segmentModel
      .find({ tenantId, periodId })
      .sort({ startDate: 1 })
      .exec();
  }
 
  async assignEmployee(tenantId: string, dto: any) {
    return this.empAssignModel.findOneAndUpdate(
      { tenantId, employeeId: dto.employeeId, periodId: dto.periodId },
      {
        $set: {
          status: 'active',
          effectiveFrom: dto.effectiveFrom || new Date(),
        },
      },
      { upsert: true, new: true },
    );
  }
 
  /** Find the active leave period for an employee on a given date */
  async resolveActivePeriod(tenantId: string, date: string) {
    return this.periodModel
      .findOne({
        tenantId,
        status: 'active',
        startDate: { $lte: date },
        endDate: { $gte: date },
      })
      .exec();
  }
}