All files / libs/payout-calculation/compute-engine/core-engine/src/engine/handlers paymentRelease.handler.ts

41.11% Statements 37/90
12.5% Branches 1/8
50% Functions 1/2
41.11% Lines 37/90

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 911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 16x 1x 1x                                                                                                           1x  
import { Injectable, Logger } from '@nestjs/common';
import { isNil, pick, uniq } from 'lodash-es';
 
import { assert, toError } from '@amalia/ext/typescript';
import { AppLogger, InjectAppLogger, withLoggerContext } from '@amalia/kernel/logger/server';
import { type PaymentReleasePayload, type TaskHandler } from '@amalia/kernel/queue/core';
import { StatementCalculationCacheFactory } from '@amalia/payout-calculation/compute-engine/core-statement-calculation-cache';
import { PlansService, RulesService } from '@amalia/payout-definition/designer/core';
import { PeriodsService } from '@amalia/payout-definition/periods/core';
import { RuleType } from '@amalia/payout-definition/plans/types';
import { CompaniesService } from '@amalia/tenants/companies/core';
 
import { StatementSaveService } from '../statementSave/statementSave.service';
 
/**
 * This event is called by the message queue to start the release of payments.
 *
 * Some payments are not released and won't be released if there is not any statement of the owner
 * of that payment in the period. It means that we should periodically try to release payments of
 * all the companies just in case it happens.
 */
@Injectable()
export class PaymentReleaseHandler implements TaskHandler<PaymentReleasePayload> {
  private readonly logger = new Logger(PaymentReleaseHandler.name);
 
  public constructor(
    private readonly planService: PlansService,
    private readonly rulesService: RulesService,
    private readonly periodService: PeriodsService,
    private readonly companiesService: CompaniesService,
    private readonly statementSaveService: StatementSaveService,
    private readonly statementCalculationCacheFactory: StatementCalculationCacheFactory,
    @InjectAppLogger() private readonly appLogger: AppLogger,
  ) {}
 
  public async handle({ companyId, periodId }: PaymentReleasePayload) {
    assert(!isNil(companyId), 'Payment Release failed: Company Id not specified.');

    const company = await this.companiesService.findById(companyId);
    assert(company, `Payment release - Company ${companyId} not found.`);

    return withLoggerContext(this.appLogger, { company: pick(company, ['id', 'name']) }, async () => {
      const period = periodId
        ? // Try to get a period from the periodId given.
          await this.periodService.findOne(company, periodId)
        : // Or get the period we're currently in.
          await this.periodService.findPeriod(company, new Date(), false).catch<null>(() => null);

      if (!period) {
        this.logger.warn(`Payment release - ${company.name}, no period found (${companyId}, ${periodId || 'N/A'})`);
        return;
      }

      const companyHoldRules = await this.rulesService.findByType(company, RuleType.HOLD_AND_RELEASE);
      const companyRulesPlans = await this.planService.findAll(company, {
        relations: ['planAssignements', 'planAssignements.user'],
        ids: companyHoldRules.map(({ planId }) => planId).filter(Boolean),
      });
      const activeUsers = uniq(
        companyRulesPlans
          .flatMap((p) => p.planAssignements)
          .filter((pa) => !pa!.user!.clearedAt)
          .map((pa) => pa!.userId),
      );

      const statementCalculationCache = await this.statementCalculationCacheFactory.instantiate(company, {
        ruleIds: companyHoldRules.map((r) => r.id),
      });

      this.logger.log(`Company ${companyId} -- Payment Release : ${activeUsers.length} users to check -- STARTED`);

      try {
        await this.statementSaveService.releasePaymentsForUsers(
          activeUsers,
          period,
          company,
          statementCalculationCache,
        );
      } catch (e) {
        const error = toError(e);
        this.logger.error({
          message: `Company ${companyId} -- Payment Release -- ERROR -- ${error.message}`,
          error,
        });
      }

      this.logger.log(`Company ${companyId} -- Payment Release -- ENDED`);
    });
  }
}