import { Proxy as FacilityProxy } from '@labshare/facility'; import { find, flatMap, flatMapDeep, groupBy, isNumber, toArray } from 'lodash'; import { BillingConstants } from '../constants/billing.constants'; import { newBillingForm } from './../spec/data/billing_data'; import { CreateExcel as createExcelSheet } from './excel'; import { ApiParams } from './models/billing'; import { QueryBuilder as qb } from './queryBuilder'; import { getLabShareConfiguration } from './common'; const conf = getLabShareConfiguration(); const billingInternalNames = [ 'Title', 'Created', 'Request_x0020_Type', 'RequestID', 'Project', 'Section', 'Request_x0020_Date', 'Client', 'CAN', 'Lab', 'LabPIName', 'RTB_Cost', 'SS_Cost', 'BilledMonth' ]; export class Billing { public createExcel = false; public qb = new qb(); public xL = new createExcelSheet(); constructor(private facilityId, private loginName) { } public async getAll() { const structeredArray = await this.structureRequest(); const apiParams = { requestBody: null, createExcel: false, generateBilled: false, arrayListItems: structeredArray, getAll: true, getRecords: false }; await this.billingApiDriver(apiParams); return; } public async filter(requestBody) { const apiParams = { requestBody, createExcel: false, generateBilled: false, arrayListItems: null, getAll: false, getRecords: false }; return await this.billingApiDriver(apiParams); } public async getRecords(requestBody) { if (requestBody.section.length === 0) { const structeredArray = await this.structureRequest(); const apiParams = { requestBody, createExcel: false, generateBilled: false, arrayListItems: structeredArray, getAll: true, getRecords: true }; return await this.billingApiDriver(apiParams); } else { const apiParams = { requestBody, createExcel: false, generateBilled: false, arrayListItems: null, getAll: true, getRecords: true }; return await this.billingApiDriver(apiParams); } } public async readyToBill(requestBody) { if (requestBody.section.length === 0) { const structeredArray = await this.structureRequest(); const apiParams = { requestBody, createExcel: true, generateBilled: false, arrayListItems: structeredArray, getAll: false, getRecords: false }; const excelItems = await this.billingApiDriver(apiParams); const excelParams = { excelItems, generateBilled: false, requestBody, loginName: this.loginName }; this.xL.buildBillingExcel(excelParams); } else { const apiParams = { requestBody, createExcel: true, generateBilled: false, arrayListItems: null, getAll: false, getRecords: false }; const excelItems = await this.billingApiDriver(apiParams); const excelParams = { excelItems, generateBilled: false, requestBody, loginName: this.loginName }; this.xL.buildBillingExcel(excelParams); } } public async generateBilled(requestBody) { if (requestBody.section.length === 0) { const structeredArray = await this.structureRequest(); const apiParams = { requestBody, createExcel: true, generateBilled: true, arrayListItems: structeredArray, getAll: false, getRecords: false }; const listItems = await this.billingApiDriver(apiParams); const excelItems = this.structureBillingItems(listItems); const excelParams = { excelItems, generateBilled: true, requestBody, loginName: this.loginName }; this.xL.buildBillingExcel(excelParams); } else { const apiParams = { requestBody, createExcel: true, generateBilled: true, arrayListItems: null, getAll: false, getRecords: false }; const listItems = await this.billingApiDriver(apiParams); const excelItems = this.structureBillingItems(listItems); const excelParams = { excelItems, generateBilled: true, requestBody, loginName: this.loginName }; this.xL.buildBillingExcel(excelParams); } } public async generateBillingSheet(requestBody) { if (requestBody.section.length === 0) { const structeredArray = await this.structureRequest(); const apiParams = { requestBody, createExcel: true, generateBilled: true, arrayListItems: structeredArray, getAll: false, getRecords: false }; const ListItems = await this.billingApiDriver(apiParams); const excelItems = this.structureBillingItems(ListItems); const excelParams = { excelItems, requestBody, loginName: this.loginName }; this.xL.buildGenBillingSheet(excelParams); } else { const apiParams = { requestBody, createExcel: true, generateBilled: true, arrayListItems: null, getAll: false, getRecords: false }; const ListItems = await this.billingApiDriver(apiParams); const excelItems = this.structureBillingItems(ListItems); const excelParams = { excelItems, requestBody, loginName: this.loginName }; this.xL.buildGenBillingSheet(excelParams); } } public async billingApiDriver(apiParams: ApiParams) { const { requestBody, createExcel, generateBilled, arrayListItems, getAll, getRecords } = apiParams; const date = requestBody && requestBody.endDate ? new Date(requestBody.endDate) : new Date(); const month = date.getUTCMonth() + 1; const day = date.getUTCDate(); const year = date.getUTCFullYear(); const queryDate = `${year}-${month}-${day}`; let proxyItems; let whereQuery; let itemsArray = []; const listArray = []; let orQuery; let requestItems; const sectionItems: any = arrayListItems || requestBody.section; const proxyList = new FacilityProxy.Facility({ facilityId: conf.billing.facilityID + BillingConstants.MYRTB_BILLING, loginName: this.loginName, }); const settings: any = await proxyList.getSettings(); const chargesList = requestBody && requestBody.chargeList ? requestBody.chargeList : settings.chargesList; // This is allow for a Generic response body and only need to account for 1 listname let continueGenerateBilling = false; for (const section of sectionItems) { for (const listNames of section.listNames) { if (generateBilled) { if (continueGenerateBilling) { continue; } proxyItems = new FacilityProxy.Items({ facilityId: conf.billing.facilityID + BillingConstants.MYRTB_BILLING, loginName: this.loginName, listName: chargesList, }); const billedMonth = month < 10 ? `${year}-0${month}` : `${year}-${month}`; whereQuery = { internalName: 'BilledMonth', value: billedMonth }; } else { proxyItems = new FacilityProxy.Items({ facilityId: section.sectionName, loginName: this.loginName, listName: listNames }); if (getAll) { whereQuery = this.qb.buildRTBQuery(section.sectionName, listNames, queryDate); if (requestBody && requestBody.status.toLowerCase() === BillingConstants.BILLED) { whereQuery = this.qb.buildBilledQuery(section.sectionName, listNames, queryDate); } } else { if (requestBody.status.toLowerCase() === BillingConstants.READY_TO_BILL) { whereQuery = this.qb.buildRTBQuery(section.sectionName, listNames, queryDate); } else if (requestBody.status.toLowerCase() === BillingConstants.BILLED) { whereQuery = this.qb.buildBilledQuery(section.sectionName, listNames, queryDate); } else { const newListItem: any = await proxyItems.getNew(); const hasSubmittedDate = find(newListItem.Properties, item => { return item.internalName === BillingConstants.SUBMITTED_DATE; }); orQuery = hasSubmittedDate ? { internalName: BillingConstants.SUBMITTED_DATE, value: queryDate, operator: 'lt' } : { internalName: BillingConstants.REQUEST_DATE, value: queryDate, operator: 'lt' }; whereQuery = { and: [ orQuery, { internalName: BillingConstants.STATUS, value: requestBody.status } ] }; } } } try { requestItems = await proxyItems.find({ where: whereQuery, orderBy: [{ internalName: BillingConstants.ID, direction: 'DESC' }] }); } catch (err) { console.error(err); continue; } if (createExcel) { if (requestItems.items.length === 0) { itemsArray.push({ listName: listNames.toUpperCase(), Section: section.sectionName.substring(9) }); } for (const item of requestItems.items) { const billingObj: any = this.extractBillingInformation(item, section); const project = this.getBillingValue(BillingConstants.PROJECT, item).value; billingObj.listName = listNames.toUpperCase(); billingObj.request = this.getBillingValue(BillingConstants.CHARGE_ID, item).value || this.getBillingValue(BillingConstants.REQUEST_ID, item).value || item.Title; billingObj.projectID = project && project.Refs && project.Refs[0] && project.Refs[0].Id; if (generateBilled) { billingObj.BilledMonth = this.getBillingValue(BillingConstants.BILLED_MONTH_CHARGE_LIST, item).value; billingObj.Request_x0020_Type = this.getBillingValue(BillingConstants.REQUEST_TYPE, item).value; billingObj.Section = this.getBillingValue(BillingConstants.SECTION, item).value; billingObj.listName = this.getBillingValue(BillingConstants.REQUEST_TYPE, item).value; billingObj.LabPIName = this.getBillingValue(BillingConstants.LABPI, item).value; } itemsArray.push(billingObj); } } else { if (getRecords) { if (requestItems.items.length === 0) { continue; } for (const item of requestItems.items) { if (item && item.ID) { itemsArray.push(item); } else { continue; } } } else { for (const item of requestItems.items) { const billingObj = this.extractBillingInformation(item, section); await this.sleep(50); this.add(billingObj, chargesList) .then(add => { if (add) { this.updateOriginalList(proxyItems, item).catch(error => { console.error(error); }); } }).catch(error => { console.error(error); }); } } } listArray.push(itemsArray); itemsArray = []; continueGenerateBilling = true; } } return createExcel || getRecords ? listArray : requestBody; } public async sleep(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } public async buildBillingObj(newBillForm, newValueObj) { for (const names in billingInternalNames) { if (billingInternalNames.hasOwnProperty(names)) { const billingObject = find(newBillForm.Properties, item => { return item.internalName === billingInternalNames[names]; }); billingObject.value = newValueObj[billingInternalNames[names]]; } newBillForm.Title = newValueObj.Title; newBillForm.ID = newValueObj.RequestID; newBillForm.Status = BillingConstants.BILL; newBillForm.CreatedDate = newValueObj.Created; newBillForm.Lab = newValueObj.Lab; } return newBillForm; } public async structureRequest() { const facilityArray = []; const structeredArray = []; const proxyList = new FacilityProxy.Items({ facilityId: conf.billing.facilityID + BillingConstants.MYRTB_BILLING, loginName: this.loginName, listName: BillingConstants.BILLING_CONFIG_LIST }); const sections = await proxyList.getView('All Items'); if (sections.items.length > 0) { for (const item of sections.items) { let value = ''; for (const listNames of item.Properties) { if (listNames.label === 'ListNames') { value = listNames.value; } } facilityArray.push({ sectionName: item.Title, listNames: value }); } } facilityArray.forEach(item => { const existing = structeredArray.filter((v, i) => { return v.sectionName === item.sectionName; }); if (existing.length) { const existingIndex = structeredArray.indexOf(existing[0]); structeredArray[existingIndex].listNames = structeredArray[existingIndex].listNames.concat(item.listNames); } else { if (typeof item.listNames === 'string') { item.listNames = [item.listNames]; } structeredArray.push(item); } }); return structeredArray; } public getBillingValue(itemValue, request) { const items = find(request.Properties, item => { return item.internalName === itemValue; }); return items && items.value ? items : { value: null }; } public extractBillingInformation(item, section) { const requestType = item.projectRef && item.projectRef.listName; const project = this.getBillingValue(BillingConstants.PROJECT, item).value; const client = this.getBillingValue(BillingConstants.CLIENT, item).value; const labPiName = this.getBillingValue(BillingConstants.LABPI, item).value; const hasSubmittedDate = find(item.Properties, billItem => { return billItem.internalName === BillingConstants.SUBMITTED_DATE; }); const requestDate = hasSubmittedDate ? this.getRequestDate(this.getBillingValue(BillingConstants.SUBMITTED_DATE, item).value) : this.getRequestDate(this.getBillingValue(BillingConstants.REQUEST_DATE, item).value); return { Title: item.Title, Created: item.CreatedDate, Request_x0020_Type: requestType, RequestID: item.ID, Project: project && project.Refs && project.Refs[0] && project.Refs[0].Value || project, Section: section.sectionName.substring(9), Request_x0020_Date: requestDate, Client: client && client.Refs && client.Refs[0] && client.Refs[0].Value, CAN: this.getBillingValue(BillingConstants.CAN, item).value, Lab: item.Lab, LabPIName: labPiName && labPiName.Refs && labPiName.Refs[0] && labPiName.Refs[0].Value, RTB_Cost: this.getBillingValue(BillingConstants.RTB_COST, item).value, SS_Cost: this.getBillingValue(BillingConstants.SS_COST, item).value, BilledMonth: this.getBillingValue(BillingConstants.BILLED_MONTH, item).value }; } public structureBillingItems(ListItems) { const itemsArray = []; const flatItems = flatMapDeep(ListItems); const groupedItems = groupBy(flatItems, 'Section'); const arrayItems = toArray(groupedItems); for (const items of arrayItems) { const groupItems = groupBy(items, 'Request_x0020_Type'); const structuredItems = toArray(groupItems); itemsArray.push(structuredItems); } return flatMap(itemsArray); } public async add(billingObj, chargeList?) { const proxyItems = new FacilityProxy.Items({ facilityId: conf.billing.facilityID + BillingConstants.MYRTB_BILLING, loginName: this.loginName, listName: chargeList, }); let newBillingObj: any = await proxyItems.getNew(); if (!newBillingObj) { newBillingObj = newBillingForm; } const billingItem = await this.buildBillingObj(newBillingObj, billingObj); return proxyItems.add(billingItem) .then(id => { return isNumber(id); }).catch(error => { console.error(error); return false; }); } public getRequestDate(requestDate) { if (!(requestDate && requestDate.includes('/Date'))) { return requestDate; } // Get the number parts const numbers = requestDate.match(/\d+/g); // Get the sign of the offset const sign = /-/.test(requestDate) ? -1 : +1; // Adjust the time value by the offset converted to milliseconds const dateinMilliseconds = +numbers[0] + sign; const dateObj = new Date(dateinMilliseconds); const month = dateObj.getUTCMonth() + 1; const day = dateObj.getUTCDate(); const year = dateObj.getUTCFullYear(); return `${month}/${day}/${year}`; } public async updateOriginalList(proxyItem, originalListItem) { const proxyItems = new FacilityProxy.Items({ facilityId: proxyItem.facilityId, loginName: this.loginName, listName: proxyItem.listName }); originalListItem.Status = BillingConstants.BILL; const hasBillDate = find(originalListItem.Properties, item => { return item.internalName === BillingConstants.BILLED_MONTH; }); const hasBillMonth = find(originalListItem.Properties, item => { return item.internalName === BillingConstants.BILLED_MONTH; }); if (hasBillDate) { const dateObj = new Date(); const month = dateObj.getUTCMonth() + 1; const day = dateObj.getUTCDate(); const year = dateObj.getUTCFullYear(); hasBillDate.value = `${month}/${day}/${year}`; } if (hasBillMonth) { const dateObj = new Date(); const month = dateObj.getUTCMonth() + 1; const year = dateObj.getUTCFullYear(); hasBillMonth.value = `${year}-${month}`; } try { proxyItems.update(originalListItem.ID, originalListItem) .catch(error => { console.error(error); return; }); } catch (error) { console.error(error); return; } } }