This commit is contained in:
Zeeshaun
2026-03-17 11:09:46 -05:00
parent bd1a9744ad
commit 6e7edcfe1b
332 changed files with 64174 additions and 0 deletions
@@ -0,0 +1,22 @@
import { AlertActionTypes } from './../actions/AlertActions';
import { alertReducer} from './AlertReducer';
describe('Alert Reducer', () => {
// No test for default case because it is unreachable with only one AlertActionType.
it ('Should return new state', () => {
// arrange
const payload = [
{ id: 1,
accountNo: "12345",
message: "Test message",
dateAdded: new Date(2019, 1, 1),
dateMarkedRead: new Date(2019, 1, 2)}];
// act
const newState = alertReducer(undefined, { type: AlertActionTypes.GET_ALL, alerts: payload});
// assert
expect(newState.alerts).toEqual(payload);
});
});
@@ -0,0 +1,74 @@
// Import Reducer type
import {Reducer} from 'redux';
import {
AlertActions,
AlertActionTypes,
} from '../actions/AlertActions';
import { Moment } from 'moment';
const cloneDeep = require('lodash.clonedeep');
export interface IAlert {
id: number;
repoId: number;
accountNo: string;
dateAdded: Moment;
dateMarkedRead?: Moment;
createdBy: string;
markedReadBy: string;
message: string;
subMessage?: string;
dayCount: number;
}
export interface IAlertState {
readonly alerts: IAlert[];
readonly actions: IAlert[];
}
// Define the initial state
const initialAlertState: IAlertState = {
alerts: [],
actions: []
};
const removeDismissedAlert = (dismissedAlert: IAlert, originalAlerts: IAlert[]): IAlert[] => {
if (dismissedAlert.dateMarkedRead) {
const foundIndex = originalAlerts.findIndex((alert) => alert.id === dismissedAlert.id);
if (foundIndex) {
const copiedAlerts = cloneDeep(originalAlerts) as IAlert[];
copiedAlerts.splice(foundIndex, 1);
return copiedAlerts;
}
}
return originalAlerts;
}
export const alertReducer: Reducer<IAlertState, AlertActions> = ( state = initialAlertState, action ) => {
switch (action.type) {
case AlertActionTypes.GET_ALL: {
return {
...state,
alerts: action.alerts,
actions: state.actions
};
}
case AlertActionTypes.GET_ALL_ACTIONS: {
return {
...state,
alerts: state.alerts,
actions: action.actions,
};
}
case AlertActionTypes.MARK_ALERT_READ: {
const alertsWithoutDismissed = removeDismissedAlert(action.alert, state.alerts);
return {
...state,
alerts: alertsWithoutDismissed,
actions: state.actions,
};
}
default:
return state;
}
};
@@ -0,0 +1,345 @@
import { LOCATION_CHANGE, LocationChangePayload } from "connected-react-router";
import { Moment } from "moment";
import { Reducer } from "redux";
import { AuctionAssignmentActions, AuctionAssignmentActionTypes } from "../actions/AuctionAssignmentActions";
import HashRouterService from "../services/HashRouterService";
import { ICollateral } from "../services/service-types/RepoAccountTypes";
import AsyncState from "../shared/enumerations/AsyncState";
import Routes from "../shared/Routes";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
import { IAuctionHouse } from "./AuctionHouseReducer";
const cloneDeep = require("lodash.clonedeep");
export interface IAutoIMS {
repoId: number;
vin: string;
auctionAssignmentId: number;
auctionCustomerNo: string;
assignmentAccepted?: Moment;
dateReceivedFromAutoIMS?: Moment;
scheduledSaleDate?: Moment;
soldDate?: Moment;
announcedConditions: string;
arbitrationReason: string;
asIs100KMiles: boolean;
auctionAge: number;
auctionGuideBookPrice: number;
buyerId: string;
buyerName: string;
chargeAmount: number;
checkAmount: number;
clientAge: number;
clientCancelled?: Moment;
collisionDamage: string;
inspectedDate?: Moment;
dateofLoss?: Moment;
datePickedUp?: Moment;
dateEnteredAutoIMS?: Moment;
daysRun: number;
daysRunHighestBidRecordedbyAuction: string;
daystoDelivery: number;
description: string;
isDrivable: boolean;
floorPriceAccepted: number;
fundsSent?: Moment;
grossPercentFloor: number;
highestBid: number;
statedMileage: number;
netPercentFloor: number;
otherAdvanceFees: number;
otherTotal: string;
otherAdvancedOther: string;
otherAdvancedStorage: string;
otherAdvancedTow: string;
otherMiscellaneous: string;
salePrice?: number;
titleNumber: string;
titleReceived?: Moment;
titleReturned?: Moment;
titleState: string;
titleStatus: string;
titleStatusfromAuction: string;
totalCharges: number;
approvedVehicleGrade: number;
isOperable: boolean;
shipmentDate?: Moment;
status: string;
storageCost: number;
storageRatePerDay: number;
towCost: number;
vehicleDecimalGradeCode: number;
vehicleGrade: number;
mmrNationalAdjustedPrice: number;
mmrRegionalAdjustedPrice: number;
mmr: string;
conditionReportLink: string;
collateralImagePath: string;
}
export interface IAuctionAssignmentToCollateral {
auctionAssignmentId: number;
collateralId: number;
collateral: ICollateral;
}
export interface IAuctionAssignment {
id: number;
repoId: number;
isActive: boolean;
auctionId: string;
auctionCustomerNo: string;
collateralVIN: string;
dateSubmitted?: Moment;
floorPrice?: number;
floorPriceResponseCode: number;
floorPriceResponseDescription: string;
holdStatus: number;
holdUntil?: Moment;
redeemedDate?: Moment;
auction?: IAuctionHouse;
autoIMS?: IAutoIMS;
sentTitleDocsDate?: Moment;
cleaningLevel: number;
legalSaleDate?: Moment;
availablePickupDate?: Moment;
auctionNotes?: string;
retailValue?: number;
auctionAssignmentToCollaterals: IAuctionAssignmentToCollateral[];
floorPriceValuesUsed?: IFloorPriceValuesUsed;
}
export interface ICollateralValuation {
id: number;
year: number;
make: string;
model: string;
uid: string;
ucgVehicleId: string;
lookupDate?: Moment;
mileage_Hours: string;
makeCode?: number;
makeDescription: string;
seriesCode?: number;
seriesDescription: string;
bodyCode?: number;
bodyDescription: string;
weight?: number;
msrp?: number;
aveMileage: number;
mileageAdjustment: number;
auctionInfoUnavailable: boolean;
lowAdjustedAuctionValue: number;
averageAdjustedAuctionValue: number;
highAdjustedAuctionValue: number;
dateRange: string;
loan?: number;
loanPlusVinAccMileage?: number;
retail?: number;
retailPlusVinAccMileage?: number;
avgTradeIn?: number;
avgTradeInPlusVinAccMileage?: number;
roughTradeIn?: number;
roughTradeInPlusVinAccMileage?: number;
tradeIn?: number;
tradeInPlusVinAccMileage?: number;
}
export interface IFloorPriceCalculationResult {
strategyName: string;
success: boolean;
floorPrice: number;
resultWeight: number;
failureReason: string;
}
export interface IFloorPriceValuesUsed {
auctionAssignmentId: number;
repoId: number;
condition?: number;
lowParam: number;
highParam: number;
percentageOf: number;
auctionGuideBookPrice?: number;
wasAuctionGuideBookPriceUsed: boolean;
mmrNationalAdjustedPrice?: number;
mmrRegionalAdjustedPrice?: number;
wasMMRNationalAdjustedPriceUsed: boolean;
wasMMRRegionalAdjustedPriceUsed: boolean;
nadaLowAdjustedAuctionValue: number;
nadaAverageAdjustedAuctionValue: number;
nadaHighAdjustedAuctionValue: number;
wasNADALowAdjustedAuctionValueUsed: boolean;
wasNADAAverageAdjustedAuctionValueUsed: boolean;
wasNADAHighAdjustedAuctionValueUsed: boolean;
nadaMmrFloorPrice?: number;
collateralValuation: ICollateralValuation;
}
export interface IFloorPriceCalculationResultSet {
primaryResult: IFloorPriceCalculationResult;
allResults: IFloorPriceCalculationResult[];
floorPriceValuesUsed: IFloorPriceValuesUsed;
}
export interface IVehicleLocation {
type?: number;
name: string;
address: string;
city: string;
state: string;
zip: string;
phone: string;
}
export interface IAuctionAssignmentPostRequest {
auctionAssignment: IAuctionAssignment;
collaterals: ICollateral[];
vehicleLocation: IVehicleLocation;
primaryCollateralId: number;
}
export interface IAuctionAssignmentState {
loadedAuctionAssignments: IAuctionAssignment[];
selectedAuctionAssignmentId: IAsyncStateContainer<number>;
currentFloorPriceCalculationSet: IAsyncStateContainer<IFloorPriceCalculationResultSet>;
}
const initialAuctionAssignmentState: IAuctionAssignmentState = {
loadedAuctionAssignments: [],
selectedAuctionAssignmentId: {
state: AsyncState.Inactive
},
currentFloorPriceCalculationSet: {
state: AsyncState.Inactive
}
};
export const validateAuctionAssignmentPostRequest = (request: IAuctionAssignmentPostRequest): boolean => {
if (
!request.auctionAssignment.auctionId ||
!request.auctionAssignment.cleaningLevel ||
!request.auctionAssignment.legalSaleDate ||
!request.auctionAssignment.availablePickupDate
) {
return false;
}
if (
!request.vehicleLocation.type ||
request.vehicleLocation.address === "" ||
request.vehicleLocation.city === "" ||
request.vehicleLocation.state === "" ||
request.vehicleLocation.zip === ""
) {
return false;
}
if (!request.collaterals.length) {
return false;
}
for (const collateral of request.collaterals) {
if (
!collateral.bodyStyle || collateral.bodyStyle === "" ||
collateral.vehicleType === undefined || collateral.vehicleType === null ||
!collateral.collateralTypeId
) {
return false;
}
}
if (!request.primaryCollateralId) {
return false;
}
return true;
};
const copyAndInsertIntoAuctionAssignments = (auctionAssignment: IAuctionAssignment | IAuctionAssignment[], loadedAuctionAssignments: IAuctionAssignment[]): IAuctionAssignment[] => {
const clonedAuctionAssignments: IAuctionAssignment[] = cloneDeep(loadedAuctionAssignments);
if (Array.isArray(auctionAssignment)) {
for (const aa of auctionAssignment) {
mutateInsertIntoAuctionAssignments(aa, clonedAuctionAssignments);
}
} else {
mutateInsertIntoAuctionAssignments(auctionAssignment, clonedAuctionAssignments);
}
return clonedAuctionAssignments;
};
/**
* WARNING: This mutates the array and is probably not the method you want!
*/
const mutateInsertIntoAuctionAssignments = (auctionAssignmentToInsert: IAuctionAssignment, auctionAssignments: IAuctionAssignment[]): void => {
const indexFound = auctionAssignments.findIndex((x) => x.id === auctionAssignmentToInsert.id);
if (indexFound >= 0) {
auctionAssignments[indexFound] = auctionAssignmentToInsert;
} else {
auctionAssignments.push(auctionAssignmentToInsert);
}
};
export const auctionAssignmentReducer: Reducer<IAuctionAssignmentState, AuctionAssignmentActions> = (state = initialAuctionAssignmentState, action) => {
switch (action.type) {
case AuctionAssignmentActionTypes.INSERT_MULTIPLE: {
return {
...state,
loadedAuctionAssignments: copyAndInsertIntoAuctionAssignments(action.auctionAssignments, state.loadedAuctionAssignments)
};
}
case AuctionAssignmentActionTypes.GET_FLOOR_PRICE: {
return {
...state,
currentFloorPriceCalculationSet: action.floorPriceCalculationResultSet,
};
}
case AuctionAssignmentActionTypes.INSERT: {
return {
...state,
loadedAuctionAssignments: copyAndInsertIntoAuctionAssignments(action.auctionAssignment, state.loadedAuctionAssignments)
};
}
case AuctionAssignmentActionTypes.INSERT_FROM_POST: {
return {
...state,
loadedAuctionAssignments: copyAndInsertIntoAuctionAssignments(action.auctionAssignmentResponse.auctionAssignment, state.loadedAuctionAssignments)
};
}
case AuctionAssignmentActionTypes.SET_SELECTED_AUCTION_ASSIGNMENT: {
return {
...state,
selectedAuctionAssignmentId: action.auctionAssignmentId
};
}
case LOCATION_CHANGE: {
return {
...state,
selectedAuctionAssignmentId: getAuctionIdFromPayload(action.payload),
currentFloorPriceCalculationSet: {
state: AsyncState.Inactive
}
};
}
default: {
return state;
}
}
};
const getAuctionIdFromPayload = (payload: LocationChangePayload): IAsyncStateContainer<number> => {
const selectorMap = HashRouterService.createHashSelector(payload.location.hash, Routes.FloorCalculation_Decl());
const auctionAssignmentId = selectorMap[Routes.AuctionAssignmentIdKey()];
if (auctionAssignmentId) {
return {
state: AsyncState.Success,
data: parseInt(auctionAssignmentId)
};
} else {
return {
state: AsyncState.Inactive
};
}
};
@@ -0,0 +1,87 @@
import { Reducer } from "redux";
import { AuctionHouseActions, AuctionHouseActionTypes } from "../actions/AuctionHouseActions";
import AuctionHouseService from "../services/AuctionHouseService";
import AsyncState from "../shared/enumerations/AsyncState";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
export interface IAuctionHouse {
auctionID: string;
name: string;
active: string;
address_Line_1: string;
address_Line_2: string;
city: string;
state: string;
zip: number;
country: string;
phone: string;
fax: string;
latitude?: number;
longitude?: number;
}
export interface IAuctionHouseState {
auctionHouses: IAsyncStateContainer<IAuctionHouse[]>;
postedAuctionHouse: IAsyncStateContainer<IAuctionHouse>;
}
const initialAuctionHouseState: IAuctionHouseState = {
auctionHouses: {
state: AsyncState.Inactive,
},
postedAuctionHouse : {
state: AsyncState.Inactive
}
};
export const auctionHouseReducer: Reducer<IAuctionHouseState, AuctionHouseActions> = (state = initialAuctionHouseState, action) => {
switch (action.type) {
case AuctionHouseActionTypes.GET_AUCTION_HOUSES: {
return {
...state,
auctionHouses: action.auctionHouses,
};
}
case AuctionHouseActionTypes.POST_AUCTION_HOUSE: {
if(action.auctionHouse.data && action.auctionHouse.state === AsyncState.Success){
return {
auctionHouses:{
data:AuctionHouseService.CopyAndInsertIntoAuctionHouses(state.auctionHouses.data,action.auctionHouse.data),
state: AsyncState.Inactive
} ,
postedAuctionHouse:{
data: action.auctionHouse.data,
state: AsyncState.Success
}
};
}else{
return{
...state,
postedAuctionHouse: action.auctionHouse
};
}
}
case AuctionHouseActionTypes.PUT_AUCTION_HOUSE: {
if(action.auctionHouse.data && action.auctionHouse.state === AsyncState.Success){
return {
auctionHouses:{
data:AuctionHouseService.CopyAndUpdateAuctionHouse(state.auctionHouses.data,action.auctionHouse.data),
state: AsyncState.Inactive
} ,
postedAuctionHouse:{
data: action.auctionHouse.data,
state: AsyncState.Success
}
};
}else{
return{
...state,
postedAuctionHouse: action.auctionHouse
};
}
}
default:
return state;
}
};
@@ -0,0 +1,102 @@
import { Moment } from "moment";
import { Reducer } from "redux";
import {CollectionsAccountActions, CollectionsAccountActionTypes} from "../actions/CollectionsAccountActions";
import { IRepoAccount } from "../services/service-types/RepoAccountTypes";
import AsyncState from "../shared/enumerations/AsyncState";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
export interface ICollectionsAccount {
accountId : number;
accountIdentifier: string;
accountNumber: string;
accountStatus: string;
accountTypeId: number;
activeFlag: boolean;
amountLastPaid?: number;
balance?: number;
bankruptcyFlag: boolean;
branch: string;
dueDate: Moment;
firstPaymentDueDate: Moment;
originalLoanAmount?: number;
originalLoanDate: Moment;
dqAmount: number;
daysDq?: number;
cases: ICollectionsCase[];
}
export interface ICollectionsCase {
caseId: number;
caseNumber: string;
caseDescription: string;
caseSTageId: number;
repoStatus: number;
caseStatusId: number;
caseTitle: string;
caseTypeBasisId: number;
caseTypeId: number;
closedDate?: Moment;
createDate: Moment;
createdUserId: number;
currentStageDate?: Moment;
currentStatusDate?: Moment;
lastReviewDate?: Moment;
nextReviewDate?: Moment;
stage1CompletionDate?: Moment;
stage2CompletionDate?: Moment;
stage3CompletionDate?: Moment;
stage4CompletionDate?: Moment;
stage5CompletionDate?: Moment;
}
export interface ICollectionsAccountState{
collectionsAccount: IAsyncStateContainer<ICollectionsAccount[]>;
searchPhrase: string;
associatedRepoAccounts: IAsyncStateContainer<IRepoAccount[]>;
didAddToQueue: IAsyncStateContainer <boolean>;
}
const initialCollectionsAccountState: ICollectionsAccountState = {
collectionsAccount: {
state: AsyncState.Inactive
},
searchPhrase: "",
associatedRepoAccounts: {
state:AsyncState.Inactive
},
didAddToQueue: {
state: AsyncState.Inactive,
data: false
}
};
export const collectionsAccountReducer: Reducer<ICollectionsAccountState, CollectionsAccountActions> = (state = initialCollectionsAccountState, action) => {
switch (action.type) {
case CollectionsAccountActionTypes.GET_COLLECTIONS_ACCOUNT: {
return {
...state,
collectionsAccount: action.collectionsAccount,
};
}
case CollectionsAccountActionTypes.SET_SEARCH_PHRASE: {
return {
...state,
searchPhrase: action.searchPhrase
};
}
case CollectionsAccountActionTypes.FIND_ASSOCIATED_REPOS: {
return {
...state,
associatedRepoAccounts: action.associatedRepoAccounts
};
}
case CollectionsAccountActionTypes.PUT_ACCOUNT_ON_QUEUE: {
return {
...state,
didAddToQueue: action.didAddToQueue
};
}
default:
return state;
}
};
@@ -0,0 +1,36 @@
import { Reducer } from "redux";
import { FactoidActions, FactoidActionTypes } from "../actions/FactoidActions";
import { IFactoid } from "../services/service-types/FactoidTypes";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
import AsyncState from "../shared/enumerations/AsyncState";
export interface IFactoidState {
readonly factoids: IAsyncStateContainer<IFactoid[]>;
readonly selectedFactoidIndex: number;
}
const initialFactoidState: IFactoidState = {
factoids: {
state: AsyncState.Inactive
},
selectedFactoidIndex: 0
};
export const factoidReducer: Reducer<IFactoidState, FactoidActions> = (state = initialFactoidState, action) => {
switch (action.type) {
case FactoidActionTypes.SET_SELECTED_FACTOID_INDEX: {
return {
...state,
selectedFactoidIndex: action.index
};
}
case FactoidActionTypes.GET_ALL_FACTOIDS: {
return {
...state,
factoids: action.factoids
};
}
default:
return state;
}
};
@@ -0,0 +1,116 @@
import moment from "moment";
import { AuctionAssignmentActionTypes, IAuctionAssignmentInsertFromPost } from "../actions/AuctionAssignmentActions";
import {IRepoAccountInsertCollateral, RepoAccountActionTypes} from "../actions/RepoAccountActions";
import { ICollateral, IRepoAccount, IRepoAccountMap } from "../services/service-types/RepoAccountTypes";
import AsyncState from "../shared/enumerations/AsyncState";
import { IAuctionAssignmentPostRequest } from "./AuctionAssignmentReducer";
import {IRepoAccountState, repoAccountReducer} from "./RepoAccountReducer";
const buildTestRepoAccount = (repoId: number, collaterals: ICollateral[]): IRepoAccount => {
return {
repoId: repoId,
collaterals: collaterals,
accountNo: "accountNo",
status: "status",
memberNo: 1,
loanNo: 1,
accountTypeCode: "code",
branch: 1,
balance: 1,
delinquentAmount: 1,
daysDelinquent: 1,
requiredPaymentAmount: 1,
origAmount: 1,
primaryIndividualId: "id",
borrowers: [],
repoStatusChanges: []
};
};
describe("Repo account reducer", () => {
const getDummRepoAccountSate = (): IRepoAccountState => {
return {
repoAccounts: {},
filteredAccounts: {
state: AsyncState.Inactive
},
selectedRepoId: {
state: AsyncState.Inactive
},
activeAccountFilters: {
state: AsyncState.Inactive,
data: {}
}
};
};
it("Returns nothing when state has no accounts", () => {
// arrange
const action: any = {type: "SOME_UNRELATED_ACTION_TYPE"};
// act
const newState = repoAccountReducer(getDummRepoAccountSate(), action);
// assert
expect(newState.repoAccounts).toEqual({});
});
it("Returns new state when account is marked retitled", () => {
// arrange state
const collateral: ICollateral = {collateralId: 1, repoId: 1, vin: "vin", make: "make", model: "model", year: 2019, repoCompanySelections: []};
const state: IRepoAccountState = {
...getDummRepoAccountSate(),
repoAccounts: {
"1": buildTestRepoAccount(1, [collateral])
}
};
// arrange action
const marked = Object.assign({}, collateral, {retitledInLandmarkName: moment()});
const action: IRepoAccountInsertCollateral = {
type: RepoAccountActionTypes.INSERT_COLLATERAL,
collateral: marked
};
// act
const newState = repoAccountReducer(state, action);
// assert
const collaterals = newState.repoAccounts[1].collaterals;
expect(collaterals.length).toEqual(1);
expect(collaterals[0]).toEqual(marked);
expect(collaterals[0]).not.toEqual(collateral);
});
it("Update from AuctionAssignmet Post Request", () => {
const collateral: ICollateral = {collateralId: 1, repoId: 1, vin: "vin", make: "make", model: "model", year: 2019, repoCompanySelections: []};
const state: IRepoAccountState = {
...getDummRepoAccountSate(),
repoAccounts: {
"1": buildTestRepoAccount(1, [collateral])
}
};
// arrange action
const newMakeCollateral = Object.assign({}, collateral, {make: "new make"});
const auctionAssignmentPostRequest: IAuctionAssignmentPostRequest = {
primaryCollateralId: 0,
auctionAssignment: {} as any,
vehicleLocation: {} as any,
collaterals: [
newMakeCollateral,
],
};
const action: IAuctionAssignmentInsertFromPost = {type: AuctionAssignmentActionTypes.INSERT_FROM_POST, auctionAssignmentResponse: auctionAssignmentPostRequest};
// act
const newState = repoAccountReducer(state, action);
// assert
const collaterals = newState.repoAccounts[1].collaterals;
expect(collaterals.length).toEqual(1);
expect(collaterals[0]).toEqual(newMakeCollateral);
expect(collaterals[0]).not.toEqual(collateral);
});
});
@@ -0,0 +1,119 @@
import {Reducer} from 'redux';
import {RepoAccountActions, RepoAccountActionTypes} from "../actions/RepoAccountActions";
import IAsyncStateContainer from '../shared/types/IAsyncStateContainer';
import AsyncState from '../shared/enumerations/AsyncState';
import { AuctionAssignmentActionTypes } from '../actions/AuctionAssignmentActions';
import { IRepoAccountMap, IRepoAccountFilters } from '../services/service-types/RepoAccountTypes';
import RepoAccountService from '../services/RepoAccountService';
import { LOCATION_CHANGE, LocationChangePayload } from 'connected-react-router';
import Routes from '../shared/Routes';
import HashRouterService from '../services/HashRouterService';
import { IPagedResponse } from '../shared/types/IPagedResponse';
export interface IRepoAccountState {
readonly repoAccounts: IRepoAccountMap;
readonly filteredAccounts: IAsyncStateContainer<IPagedResponse<number>>;
readonly selectedRepoId: IAsyncStateContainer<number>;
readonly accountFilters: IRepoAccountFilters;
readonly searchPhrase: string;
}
const initialRepoAccountState: IRepoAccountState = {
repoAccounts: {},
filteredAccounts: {
state: AsyncState.Inactive,
data: {
items: [],
totalItems: 0,
currentPage: 1,
totalPages: 1
}
},
selectedRepoId: {
state: AsyncState.Inactive,
},
accountFilters: {onlyOpenAccounts: true},
searchPhrase: ''
};
export const repoAccountReducer: Reducer<IRepoAccountState, RepoAccountActions> = (state = initialRepoAccountState, action) => {
switch (action.type) {
case RepoAccountActionTypes.INSERT_ACCOUNTS: {
return {
...state,
repoAccounts: RepoAccountService.CreateAmmendedRepoAccountMap(state.repoAccounts, action.repoAccounts),
};
}
case RepoAccountActionTypes.INSERT_ONE_ACCOUNT: {
return {
...state,
repoAccounts: RepoAccountService.CreateAmmendedRepoAccountMap(state.repoAccounts, action.repoAccount),
};
}
case RepoAccountActionTypes.SET_SELECTED_ACCOUNT: {
return {
...state,
selectedRepoId: action.repoId,
};
}
case RepoAccountActionTypes.SET_FILTERED_ACCOUNTS: {
return {
...state,
filteredAccounts: action.filteredAccounts
};
}
case RepoAccountActionTypes.SET_FILTERS: {
return {
...state,
accountFilters: action.filters
};
}
case RepoAccountActionTypes.INSERT_COLLATERAL: {
return {
...state,
repoAccounts: RepoAccountService.CreateUpdatedRepoAccountsFromCollateral(state.repoAccounts, action.collateral),
};
}
case RepoAccountActionTypes.UPDATE_REFUND_STATUS: {
return {
...state,
repoAccounts: RepoAccountService.CreateUpdatedRepoAccountsFromRefundStatus(state.repoAccounts, action.refundStatus)
}
}
case RepoAccountActionTypes.SET_SEARCH_PHRASE: {
return {
...state,
searchPhrase: action.searchPhrase
}
}
case AuctionAssignmentActionTypes.INSERT_FROM_POST: {
return {
...state,
repoAccounts: RepoAccountService.CreateUpdatedRepoAccountsFromCollateral(state.repoAccounts, action.auctionAssignmentResponse.collaterals),
}
}
case LOCATION_CHANGE: {
return {
...state,
selectedRepoId: getRepoIdFromPayload(action.payload)
}
}
default:
return state;
}
};
const getRepoIdFromPayload = (payload: LocationChangePayload): IAsyncStateContainer<number> => {
const selectorMap = HashRouterService.createHashSelector(payload.location.hash, Routes.Account_Decl());
const repoId = selectorMap[Routes.RepoIdKey()];
if (repoId) {
return {
state: AsyncState.Success,
data: parseInt(repoId)
};
} else {
return {
state: AsyncState.Inactive
};
}
}
@@ -0,0 +1,85 @@
import { Moment } from "moment";
import { Reducer } from "redux";
import { RepoCompanyActions, RepoCompanyActionTypes } from "../actions/RepoCompanyActions";
import AsyncState from "../shared/enumerations/AsyncState";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
export interface IRepoCompany {
id: number;
referenceId?: number;
name: string;
email?: string;
address_Line_1?: string;
address_Line_2?: string;
city?: string;
state?: string;
zip?: string;
country?: string;
phone?: string;
fax?: string;
readonly disabledBy?: string;
disabledDate?: Moment;
}
export interface IRepoCompanyState {
repoCompanies: IAsyncStateContainer<IRepoCompany[]>;
}
const initialCompanyState: IRepoCompanyState = {
repoCompanies: {
state: AsyncState.Inactive
}
};
const concatRepoCompanies = (repoCompanies: IAsyncStateContainer<IRepoCompany[]>, newRepoCompany: IRepoCompany): IAsyncStateContainer<IRepoCompany[]> => {
if (!repoCompanies.data) {
return {
state: repoCompanies.state,
data: [newRepoCompany]
};
}
const copiedRepoCompanies = repoCompanies.data.concat([]);
const indexOfCompany = copiedRepoCompanies.findIndex((repoCompany) => repoCompany.id === newRepoCompany.id);
if (indexOfCompany >= 0) {
copiedRepoCompanies[indexOfCompany] = newRepoCompany;
} else {
copiedRepoCompanies.push(newRepoCompany);
}
return {
state: repoCompanies.state,
data: copiedRepoCompanies
};
};
/**
* NOTE: This mutates the parameter
*/
const sortRepoCompanies = (repoCompanies: IAsyncStateContainer<IRepoCompany[]>): void => {
if (repoCompanies.data) {
repoCompanies.data.sort((a, b) =>
a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 0
);
}
};
export const repoCompanyReducer: Reducer<IRepoCompanyState, RepoCompanyActions> = (state = initialCompanyState, action) => {
switch (action.type) {
case RepoCompanyActionTypes.SET_REPO_COMPANIES: {
sortRepoCompanies(action.repoCompanies);
return {
repoCompanies: action.repoCompanies
};
}
case RepoCompanyActionTypes.INSERT_SINGLE_REPO_COMPANY: {
const updatedRepoCompanies = concatRepoCompanies(state.repoCompanies, action.repoCompany);
sortRepoCompanies(updatedRepoCompanies);
return {
repoCompanies: updatedRepoCompanies
};
}
default:
return state;
}
};
@@ -0,0 +1,68 @@
import { getCompareValueForCells } from "./ReportingReducer";
describe("Reporting Reducer", () => {
it("getCompareValueForCells() Sorts Two Decimal Values", () => {
const value = getCompareValueForCells(
{ display: "Shouldn't Matter", value: 1 },
{ display: "Shouldn't Matter", value: -1 }
);
expect(value).toBe(2);
});
it("getCompareValueForCells() Sorts Valid / null Values (1)", () => {
const value = getCompareValueForCells(
{ display: "Shouldn't Matter", value: null },
{ display: "Shouldn't Matter", value: -2 }
);
expect(value).toBe(-1);
});
it("getCompareValueForCells() Sorts Valid / null Values (2)", () => {
const value = getCompareValueForCells(
{ display: "Shouldn't Matter", value: -2 },
{ display: "Shouldn't Matter", value: null }
);
expect(value).toBe(1);
});
it("getCompareValueForCells() Sorts Valid / undefined Values (1)", () => {
const value = getCompareValueForCells(
{ display: "Shouldn't Matter" },
{ display: "Shouldn't Matter", value: -3 }
);
expect(value).toBe(-1);
});
it("getCompareValueForCells() Sorts Valid / undefined Values (2)", () => {
const value = getCompareValueForCells(
{ display: "Shouldn't Matter", value: 5 },
{ display: "Shouldn't Matter" }
);
expect(value).toBe(1);
});
it("getCompareValueForCells() Sorts Display", () => {
const value = getCompareValueForCells(
{ display: "B" },
{ display: "a" }
);
expect(value).toBe(1);
});
it("getCompareValueForCells() Sorts String Value", () => {
const value = getCompareValueForCells(
{ display: "B", value: "a" },
{ display: "a", value: "B" }
);
expect(value).toBe(-1);
});
});
@@ -0,0 +1,94 @@
import moment, {Moment} from "moment";
import {Reducer} from "redux";
import {ReportingActions, ReportingActionTypes} from "../actions/ReportingActions";
import AsyncState from "../shared/enumerations/AsyncState";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
export interface IReportDataTableCell {
display: string;
value?: any;
}
export interface IReportReferenceRow {
repoId: number;
cells: IReportDataTableCell[];
}
export interface IReportTable {
headers: string[];
cells: IReportDataTableCell[][];
}
export interface IDataTable {
headers: string[];
rows: IReportReferenceRow[];
}
export interface IReport {
reportTable?: IReportTable;
dataTable: IDataTable;
}
export interface IReportingState {
reportForm: IAdminReportForm;
currentReport: IAsyncStateContainer<IReport>;
}
export interface IReportUrlDisplayMap {
display: string;
url: string;
}
export interface IAdminReportForm {
reportUrl?: IReportUrlDisplayMap;
lowDate?: Moment;
highDate?: Moment;
}
export const getCompareValueForCells = (a: IReportDataTableCell, b: IReportDataTableCell): number => {
const isValid = (value: any) => {
return value !== undefined && value !== null;
};
if (!isValid(a.value) && !isValid(b.value)) {
return a.display.toLowerCase() < b.display.toLowerCase() ? -1 : 1;
}
if (isValid(a.value) && isValid(b.value)) {
if (typeof a.value === "string" && typeof b.value === "string") {
return a.value.toLowerCase() < b.value.toLowerCase() ? -1 : 1;
} else {
return (a.value - b.value);
}
}
if (!isValid(a.value)) {
return -1;
} else {
return 1;
}
};
const initialReportingState: IReportingState = {
reportForm: {
lowDate: moment().subtract(1, "years"),
highDate: moment()
},
currentReport: {
state: AsyncState.Inactive
}
};
export const reportingReducer: Reducer<IReportingState, ReportingActions> = (state = initialReportingState, action) => {
switch (action.type) {
case ReportingActionTypes.SET_REPORT: {
return {
...state,
reportForm: action.reportForm,
currentReport: action.currentReport,
};
}
default:
return state;
}
};
@@ -0,0 +1,81 @@
import { Reducer } from "redux";
import { SavedLocationActions, SavedLocationActionTypes } from "../actions/SavedLocationActions";
import AsyncState from "../shared/enumerations/AsyncState";
import IAsyncStateContainer from "../shared/types/IAsyncStateContainer";
export interface ISavedLocation {
id: number;
name: string;
locationType: number;
address_Line_1: string;
address_Line_2?: string;
city: string;
state: string;
zip: string;
country?: string;
phone?: string;
fax?: string;
}
export interface ISavedLocationState {
savedLocations: IAsyncStateContainer<ISavedLocation[]>;
}
const initialLocationState: ISavedLocationState = {
savedLocations: {
state: AsyncState.Inactive
}
};
const concatSavedLocations = (savedLocations: IAsyncStateContainer<ISavedLocation[]>, newSavedLocation: ISavedLocation): IAsyncStateContainer<ISavedLocation[]> => {
if (!savedLocations.data) {
return {
state: savedLocations.state,
data: [newSavedLocation]
};
}
const copiedSavedLocations = savedLocations.data.concat([]);
const indexOfLocation = copiedSavedLocations.findIndex((savedLocation) => savedLocation.id === newSavedLocation.id);
if (indexOfLocation >= 0) {
copiedSavedLocations[indexOfLocation] = newSavedLocation;
} else {
copiedSavedLocations.push(newSavedLocation);
}
return {
state: savedLocations.state,
data: copiedSavedLocations
};
};
/**
* NOTE: This mutates the parameter
*/
const sortSavedLocations = (savedLocations: IAsyncStateContainer<ISavedLocation[]>): void => {
if (savedLocations.data) {
savedLocations.data.sort((a, b) =>
a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 0
);
}
};
export const savedLocationReducer: Reducer<ISavedLocationState, SavedLocationActions> = (state = initialLocationState, action) => {
switch (action.type) {
case SavedLocationActionTypes.SET_SAVED_LOCATIONS: {
sortSavedLocations(action.savedLocations);
return {
savedLocations: action.savedLocations
};
}
case SavedLocationActionTypes.INSERT_SINGLE_SAVED_LOCATION: {
const updatedSavedLocations = concatSavedLocations(state.savedLocations, action.savedLocation);
sortSavedLocations(updatedSavedLocations);
return {
savedLocations: updatedSavedLocations
};
}
default:
return state;
}
};
@@ -0,0 +1,36 @@
import {UserInfo} from "react-adal";
import {Reducer} from "redux";
import {SecurityActions, SecurityActionTypes} from "../actions/SecurityActions";
export interface ISecurityState {
readonly userInfo: UserInfo;
readonly userToken: string;
}
const initialSecurityState: ISecurityState = {
userInfo: {
profile: {},
userName: "Unknown"
},
userToken: "invalid"
};
export const securityReducer: Reducer<ISecurityState, SecurityActions> = (state = initialSecurityState, action) => {
switch (action.type) {
case SecurityActionTypes.APPLY_USER: {
return {
...state,
userInfo: action.user
};
}
case SecurityActionTypes.APPLY_TOKEN: {
return {
...state,
userToken: action.userToken
};
}
default:
return state;
}
};
@@ -0,0 +1,144 @@
import { ICollateralType, ICollateralTypesToVehicleType, getCollateralTypesFromVehicleType, IAllowedStatusChange, getViableStatuses } from "./SettingsReducer";
describe('Settings Reducer', () => {
const collateralTypes: ICollateralType[] = [
{
id: 1,
description: 'Collateral Type 1',
lookupHandler: '',
},
{
id: 4,
description: 'Collateral Type 4',
lookupHandler: '',
},
{
id: 2,
description: 'Collateral Type 2',
lookupHandler: '',
},
{
id: 3,
description: 'Collateral Type 3',
lookupHandler: '',
},
];
const collateralTypeToVehicleTypes: ICollateralTypesToVehicleType[] = [
{
id: 1,
vehicleTypeId: 1,
collateralTypeId: 1,
},
{
id: 2,
vehicleTypeId: 1,
collateralTypeId: 3,
},
{
id: 3,
vehicleTypeId: 2,
collateralTypeId: 10,
},
{
id: 2,
vehicleTypeId: 1,
collateralTypeId: 3,
},
{
id: 1,
vehicleTypeId: 1,
collateralTypeId: 1,
},
];
it('Get Valid Collateral Types from Vehicle Type', () => {
const foundCollateral = getCollateralTypesFromVehicleType(
collateralTypeToVehicleTypes,
collateralTypes,
1,
);
expect(foundCollateral).toEqual([
{
id: 1,
description: 'Collateral Type 1',
lookupHandler: '',
},
{
id: 3,
description: 'Collateral Type 3',
lookupHandler: '',
},
]);
});
it('Get Empty Array For Un-Matched Vehciel Type', () => {
const foundCollateral = getCollateralTypesFromVehicleType(
collateralTypeToVehicleTypes,
collateralTypes,
5,
);
expect(foundCollateral).toEqual([]);
});
it('Get Empty Array For Un-Matched Collateral Type', () => {
const foundCollateral = getCollateralTypesFromVehicleType(
collateralTypeToVehicleTypes,
collateralTypes,
2,
);
expect(foundCollateral).toEqual([]);
});
it('Get Empty Array For Undefined Vehciel Type', () => {
const foundCollateral = getCollateralTypesFromVehicleType(
collateralTypeToVehicleTypes,
collateralTypes,
);
expect(foundCollateral).toEqual([]);
});
it ('Viable status changes produces correct relations', () => {
const dummyAllowedStatusChanges: IAllowedStatusChange[] = [
{
id: 0,
currentStatus: 'A',
allowedChangeStatus: 'B',
},
{
id: 1,
currentStatus: 'A',
allowedChangeStatus: 'B',
},
{
id: 1,
currentStatus: 'A',
allowedChangeStatus: 'B',
},
{
id: 1,
currentStatus: 'A',
allowedChangeStatus: ' B ',
},
{
id: 2,
currentStatus: 'A',
allowedChangeStatus: 'C',
},
{
id: 3,
currentStatus: 'H',
allowedChangeStatus: 'A',
},
{
id: 4,
currentStatus: 'A',
allowedChangeStatus: 'A',
}
];
expect(getViableStatuses('A', dummyAllowedStatusChanges)).toStrictEqual(['A', 'B', 'C']);
expect(getViableStatuses('H', dummyAllowedStatusChanges)).toStrictEqual(['H', 'A']);
expect(getViableStatuses('F', dummyAllowedStatusChanges)).toStrictEqual(['F']);
});
});
@@ -0,0 +1,157 @@
import { Reducer } from "redux";
import { SettingsActions, SettingsActionTypes } from "../actions/SettingsActions";
export interface IAccountType {
id: number;
accountTypeCode: string;
description: string;
isVisible: boolean;
categoryId: number;
}
export interface IAllowedStatusChange {
id: number;
currentStatus: string;
allowedChangeStatus: string;
}
export interface IBodyStyle {
code: string;
description: string;
}
export interface ICleaningLevel {
code: number;
description: string;
}
export interface ICollateralType {
id: number;
description: string;
lookupHandler: string;
}
export interface ICollateralTypesToVehicleType {
id: number;
collateralTypeId: number;
vehicleTypeId: number;
}
export interface IHoldStatus {
code: number;
description: string;
}
export interface ILoanCategory {
id: number;
categoryName: string;
}
export interface ILocationType {
code: number;
description: string;
}
export interface IVehicleType {
code: string;
description: string;
}
export interface IBranchSetEntry {
branchId: number;
branchName: string;
}
export interface IExternalUser {
id: number;
name: string;
code: string;
referenceId: number;
}
export interface ISettingsState {
readonly accountTypes: IAccountType[];
readonly allowedStatusChanges: IAllowedStatusChange[];
readonly bodyStyles: IBodyStyle[];
readonly cleaningLevels: ICleaningLevel[];
readonly collateralTypes: ICollateralType[];
readonly collateralTypesToVehicleTypes: ICollateralTypesToVehicleType[];
readonly holdStatuses: IHoldStatus[];
readonly loanCategories: ILoanCategory[];
readonly locationTypes: ILocationType[];
readonly vehicleTypes: IVehicleType[];
readonly branchSet: IBranchSetEntry[];
readonly externalUsers: IExternalUser[];
readonly repoTypeMap: string[];
readonly transactionTypeCodes: string[];
readonly transactionTypeCodeQueryPrefix: string;
readonly transactionHistoryParameterDateFormat: string;
readonly transactionHistoryStartDateOffset: number;
}
const initialSettingsState: { holdStatuses: any[]; loanCategories: any[]; vehicleTypes: any[]; bodyStyles: any[]; externalUsers: any[]; allowedStatusChanges: any[]; cleaningLevels: any[]; branchSet: any[]; collateralTypes: any[]; repoTypeMap: any[]; locationTypes: any[]; collateralTypesToVehicleTypes: any[]; accountTypes: any[]; transactionTypeCodes: any[]; transactionTypeCodeQueryPrefix: any; transactionHistoryParameterDateFormat: any; transactionHistoryStartDateOffset: any } = {
accountTypes: [],
allowedStatusChanges: [],
bodyStyles: [],
cleaningLevels: [],
collateralTypes: [],
collateralTypesToVehicleTypes: [],
holdStatuses: [],
loanCategories: [],
locationTypes: [],
vehicleTypes: [],
branchSet: [],
externalUsers: [],
repoTypeMap: [],
transactionTypeCodes: [],
transactionTypeCodeQueryPrefix: "",
transactionHistoryParameterDateFormat: "",
transactionHistoryStartDateOffset: 0
};
export const getViableStatuses = (currentStatus: string, allowedStatusChanges: IAllowedStatusChange[]) => {
currentStatus = currentStatus.trim();
const viableStatuses: string[] = [currentStatus];
for (const allowedStatusChange of allowedStatusChanges) {
if (allowedStatusChange.currentStatus.trim() === currentStatus && viableStatuses.indexOf(allowedStatusChange.allowedChangeStatus.trim()) === -1) {
viableStatuses.push(allowedStatusChange.allowedChangeStatus);
}
}
return viableStatuses;
};
export const getCollateralTypesFromVehicleType = (collateralToVehicleTypes: ICollateralTypesToVehicleType[], collateralTypes: ICollateralType[], vehicleTypeCode?: number) => {
if (vehicleTypeCode === undefined || vehicleTypeCode === null) {
return [];
}
const uniqueCollateralTypeIDs: number[] = [];
for (const c2v of collateralToVehicleTypes) {
if (
c2v.vehicleTypeId === vehicleTypeCode &&
uniqueCollateralTypeIDs.indexOf(c2v.collateralTypeId) === -1
) {
uniqueCollateralTypeIDs.push(c2v.collateralTypeId);
}
}
const matchedCollateralTypes: ICollateralType[] = [];
for (const collateralTypeID of uniqueCollateralTypeIDs) {
const foundCollateralType = collateralTypes.find((ct => ct.id === collateralTypeID));
if (foundCollateralType) {
matchedCollateralTypes.push(foundCollateralType);
}
}
return matchedCollateralTypes;
};
export const settingsReducer: Reducer<ISettingsState, SettingsActions> = (state = initialSettingsState, action) => {
switch (action.type) {
case SettingsActionTypes.GET_SETTINGS: {
return action.settings;
}
default:
return state;
}
};
@@ -0,0 +1,43 @@
import { Reducer } from 'redux';
import { VehicleFloorPricingParametersActions, VehicleFloorPricingParametersActionTypes } from "../actions/VehicleFloorPricingParametersActions";
export interface IVehicleFloorPricingParameters {
id: number;
lowCondition?: number;
highCondition?: number;
nadaFirstValue?: string;
nadaSecondValue?: string;
mmrAdjustedValue?: string;
percentageOf?: number;
}
export interface IVehicleFloorPricingParametersState {
readonly vehicleFloorPricingParameters: IVehicleFloorPricingParameters[];
}
export interface VehicleFloorPricingParametersPutRequest {
vehicleFloorPricingParameters: IVehicleFloorPricingParameters[]
}
const initialVehicleFloorPricingParametersState: IVehicleFloorPricingParametersState = {
vehicleFloorPricingParameters: []
}
export const vehicleFloorPricingParametersReducer: Reducer<IVehicleFloorPricingParametersState, VehicleFloorPricingParametersActions> = (state = initialVehicleFloorPricingParametersState, action) => {
switch (action.type) {
case VehicleFloorPricingParametersActionTypes.GET_VEHICLE_FLOOR_PRICING_PARAMETERS: {
return {
...state,
vehicleFloorPricingParameters: action.vehicleFloorPricingParameters
};
}
case VehicleFloorPricingParametersActionTypes.PUT_VEHICLE_FLOOR_PRICING_PARAMETERS: {
return {
...state,
vehicleFloorPricingParameters: action.vehicleFloorPricingParameters
};
}
default:
return state;
}
}