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}` ); } }) ); } }
Preview:
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