Preview:
import { Inject, Service } from 'typedi';
import { FindOptions } from '../base/FindOptions';
import { PartnerService } from '../partner/PartnerService';
import { SubmissionPeriod } from './SubmissionPeriod';
import { SubmissionPeriodInput } from './SubmissionPeriodInput';
import { Operator } from '../base/filters/Operator';
import { ServiceError } from '../base/ServiceError';
import { User } from '../user/User';
import { AuditEventService } from '../event/AuditEventService';
import { CustomerDomainEntityService } from '../base/CustomerDomainEntityService';
import { SubmissionScheduleAuditService } from './SubmissionScheduleAuditService';
import { AuditEvent } from '../event/AuditEvent';
import { ServiceUserService } from '../user/ServiceUserService';
import { AuditTypeService } from '../event/AuditTypeService';
import { SubmissionScheduleAudit } from './SubmissionScheduleAudit';
import { AuditType } from '../event/AuditType';

export const NO_DATA_ACTION_TYPE = 'SubmissionScheduleNoDataReported';

@Service()
export class SubmissionPeriodService extends CustomerDomainEntityService<SubmissionPeriod> {
  @Inject()
  protected partnerService: PartnerService;

  @Inject()
  protected auditEventService: AuditEventService;

  @Inject()
  protected submissionScheduleAuditService: SubmissionScheduleAuditService;

  @Inject()
  protected serviceUserService: ServiceUserService;

  @Inject()
  protected auditTypeService: AuditTypeService;

  constructor() {
    super(SubmissionPeriod);
  }

  getServiceName() {
    return 'SubmissionPeriod';
  }

  async findSubmissionPeriods(
    customerId: string,
    partnerId: string,
    options: FindOptions = new FindOptions()
  ): Promise<SubmissionPeriod[]> {
    const customer = await this.customerService.findOneById(customerId);
    const partner = await this.partnerService.findOneById(partnerId);
    const { offset, limit, filters = {}, sort } = options;

    // return empty array if customer or partner is invalid
    if (!customer || (!partner && !PartnerService.isAll(partnerId))) {
      console.error(
        'Customer ' + customerId + ' or partner ' + partnerId + ' are invalid'
      );
      return [];
    }

    const query = this.repository
      .createQueryBuilder('sp')
      .innerJoinAndSelect(
        'sp.submissionSchedule',
        'ss',
        '"sp"."CUSTOMER_SID" = "ss"."CUSTOMER_SID"'
      )
      .innerJoinAndSelect('ss.dataType', 'dt')
      .innerJoinAndSelect('ss.reportingPartner', 'rp')
      .innerJoinAndSelect('rp.partnerOverlayView', 'csr')
      .leftJoinAndSelect(
        'sp.submissionPeriodLineItemView',
        'spli',
        '"sp"."CUSTOMER_SID" = "spli"."CUSTOMER_SID"'
      )
      .leftJoinAndSelect(
        'sp.dataFileSummaryInfo',
        'dfsi',
        '"sp"."CUSTOMER_SID" = "dfsi"."CUSTOMER_SID"'
      )
      .offset(offset)
      .limit(limit);

    this.buildWhere(filters, query);

    query.andWhere(`"sp"."CUSTOMER_SID" = ${customer.sid}`)
      .andWhere(`"sp"."DELETED" = 0`)
      .andWhere(`"csr"."CUSTOMER_SID" = ${customer.sid}`);

    if (partner) {
      query.andWhere('"ss"."REPORTING_PARTNER_SID" = ' + partner.sid);
    }
    this.addOrderBys(query, sort ?? {});
    let periods: SubmissionPeriod[] = await query.getMany();
    periods = periods.map((period) => {
      if (period.fileIds) {
        period.firstFileId = period.fileIds.split(',')[0];
      }
      return period;
    });

    periods = await this.loadFirstFileFields(customer.sid, periods);

    return periods;
  }

  async loadFirstFileFields(
    customerSid: number,
    periods: SubmissionPeriod[]
  ): Promise<SubmissionPeriod[]> {

    if (periods.length === 0) return periods;

    // construct SQL
    const sql =
      ` select ID "id", FILE_NAME "file_name", CREATE_DATE "create_date" 
        from data_file 
        where customer_sid = ${customerSid} and id in (:ids) `;

    // execute SQL
    let rows = new Map();

    for (let i = 0; i < periods.length; i += 1000) {
      let subSet = periods.slice(i, i + 1000);
      let dfIds = [];
      for (let j = 0; j < subSet.length; j++) {
        if (subSet[j].firstFileId) {
          dfIds.push(`'${subSet[j].firstFileId}'`);
        }
      }
      if (dfIds.length === 0) continue;
      //not a param to avoid bind var peaking
      let inListSql = sql.replace(':ids', dfIds.join(','));

      let results = await this.repository.query(inListSql);
      results.map((r) => {
        rows.set(r.id, r);
      });
    }
    // map SQL result
    return periods.map((period) => {
      if (period.firstFileId) {
        period.firstFileName = rows.get(period.firstFileId).file_name;
        period.firstFileCreateDate = rows.get(period.firstFileId).create_date;
      }
      return period;
    });
  }

  async markNoData(
    customerId: string,
    user: User,
    data: Partial<SubmissionPeriodInput>[]
  ): Promise<ServiceError[]> {
    let sids: number[] = data.map((input: SubmissionPeriodInput) => {
      return input.sid;
    });

    const options: FindOptions = {
      offset: 0,
      limit: 1000,
      filters: {
        sid: {
          operator: Operator.IN,
          values: sids
        }
      }
    };

    const submissionPeriods = await this.findSubmissionPeriods(
      customerId,
      user.partnerId,
      options
    );

    if (submissionPeriods.length <= 0) {
      throw new Error(`Customer ${customerId} or partner ${user.partnerId} are invalid`);
    }

    const serviceUser = await this.serviceUserService.findByLogin(user.nucleusUsername);
    if (!serviceUser) {
      throw new Error(`Service user not found for login ${user.id}`);
    }

    const auditType = await this.auditTypeService.findOneByName(
      NO_DATA_ACTION_TYPE
    );

    let errors: Array<ServiceError> = [];
    let submissionPeriodMap: Map<Number, SubmissionPeriod> = new Map();
    let validInput: Partial<SubmissionPeriodInput>[] = [];

    submissionPeriods.forEach((submissionPeriod) => {
      submissionPeriodMap.set(Number(submissionPeriod.sid), submissionPeriod);
    });

    data.forEach((submissionPeriodInput) => {
      submissionPeriodInput.noData = true;
      submissionPeriodInput.noDataCreateDate = new Date();
      submissionPeriodInput.noDataServiceUserSid = serviceUser.sid;

      let submissionPeriod = submissionPeriodMap.get(Number(submissionPeriodInput.sid))
      let err = submissionPeriod ? this.validateForMarkNoData(submissionPeriod) : null;
      if (submissionPeriod && !err) validInput.push(submissionPeriodInput);
      errors.push(err);
    });

    if (validInput.length === 0) return errors;

    const updateServiceErrors: Array<ServiceError> = await this.updateSubmissionPeriods(
      customerId,
      user,
      validInput,
      auditType
    );

    let dataIndex = 0;
    let validDataIndex = 0;
    data.forEach((submissionPeriodInput) => {
      if (validDataIndex < validInput.length && submissionPeriodInput.sid == validInput[validDataIndex].sid) {
        if (!errors[dataIndex]) {
          errors[dataIndex++] = updateServiceErrors[validDataIndex++];
        }
      } else {
        dataIndex++;
      }
    });

    return errors;
  }

  validateForMarkNoData(submissionPeriod: SubmissionPeriod): ServiceError | null {
    let comparisonDate: Date = new Date();
    comparisonDate.setDate(comparisonDate.getDate() - 31);
    if (submissionPeriod.reportedFlag) {
      return new ServiceError('REPORTED_DATA_ERROR', 'Submission Period already has reported data, so No-Data-To-Report is not applicable.');
    } else if (submissionPeriod.noDataReason != null) {
      return new ServiceError('ALREAD_MARKED_NO_DATA_ERROR', 'Submission Period is already marked as No-Data-To-Report.');
    } else if (submissionPeriod.fileIds) {
      return new ServiceError('LINE_COUNT_ERROR', 'Submission Period already has line items count, so No-Data-To-Report is not applicable.');
    } else if (submissionPeriod.periodEndDate.getTime() < comparisonDate.getTime()) {
      return new ServiceError('GRACE_PERIOD_ERROR', 'Submission Period is beyond the grace period to mark as No-Data-To-Report.');
    }
    return null;
  }

  async updateSubmissionPeriods(
    customerId: string,
    user: User,
    data: Partial<SubmissionPeriodInput>[],
    auditType?: AuditType,
  ): Promise<ServiceError[]> {
    const customer = await this.customerService.findOneById(customerId);
    const partner = await this.partnerService.findOneById(user.partnerId);
    const serviceUser = await this.serviceUserService.findByLogin(user.nucleusUsername);

    if (!serviceUser || serviceUser.sid === 0) {
      throw new Error(`Service user not found for login ${user.id}`);
    }

    let sids: number[] = data.map((input: SubmissionPeriodInput) => {
      return input.sid;
    });

    const options: FindOptions = {
      offset: 0,
      limit: 1000,
      filters: {
        sid: {
          operator: Operator.IN,
          values: sids
        }
      }
    };

    const submissionPeriods = await this.findSubmissionPeriods(
      customerId,
      user.partnerId,
      options
    );

    let submissionPeriodMap = new Map<number, SubmissionPeriod>();
    submissionPeriods.map((submissionPeriod) => {
      submissionPeriodMap.set(Number(submissionPeriod.sid), submissionPeriod);
    });

    return await Promise.all(
      data.map(async (submissionPeriodInput) => {
        try {
          const submissionPeriod = submissionPeriodMap.get(
            Number(submissionPeriodInput.sid)
          );
          if (submissionPeriod) {
            await this.update(
              submissionPeriod.sid,
              Object.assign({}, submissionPeriodInput)
            );
          }

          if (auditType) {
            let auditEvent = new AuditEvent();
            auditEvent = await this.auditEventService.createAuditEvent(
              Object.assign({}, auditEvent, {
                serviceUserSid: serviceUser.sid,
                eventTimeStamp: new Date(),
                customerSid: customer.sid,
                reportingPartnerSid: partner ? partner.sid : null,
                auditTypeSid: auditType.sid,
                parentSid: null,
                createDate: new Date()
              })
            );

            const submissionScheduleAudit = new SubmissionScheduleAudit();
            await this.submissionScheduleAuditService.createSubmissionScheduleAudit(
              Object.assign({}, submissionScheduleAudit, {
                submissionPeriodSid: submissionPeriod.sid,
                submissionScheduleSid: submissionPeriod.submissionScheduleSid,
                customerSid: customer.sid,
                details: null,
                auditEventSid: auditEvent.sid,
                createDate: new Date()
              })
            );
          }

          return null;
        } catch (err) {
          console.log(err);
          return new ServiceError(
            'SUBMISSION_PERIOD_ERR',
            `Submission period could not be updated for : ${submissionPeriodInput.sid}`
          );
        }
      })
    );
  }
}
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter