client/components/mma/holiday/HolidayStopApi.ts (195 lines of code) (raw):
import type { DateRange, ParsedDate } from '../../../../shared/dates';
import {
DATE_FNS_INPUT_FORMAT,
dateAddYears,
dateRange,
dateString,
getOldestDate,
parseDate,
} from '../../../../shared/dates';
import { MDA_TEST_USER_HEADER } from '../../../../shared/productResponse';
import { AsyncLoader } from '../shared/AsyncLoader';
interface CommonCreditProperties {
estimatedPrice?: number;
actualPrice?: number;
}
interface RawHolidayStopDetail extends CommonCreditProperties {
publicationDate: string;
invoiceDate?: string;
}
export interface HolidayStopDetail extends CommonCreditProperties {
publicationDate: ParsedDate;
invoiceDate?: ParsedDate;
}
interface MutabilityFlags {
isFullyMutable: boolean;
isEndDateEditable: boolean;
}
interface RawHolidayStopRequest {
startDate: string;
endDate: string;
id: string;
subscriptionName: string;
publicationsImpacted: RawHolidayStopDetail[];
mutabilityFlags: MutabilityFlags;
withdrawnTime?: string;
bulkSuspensionReason?: string;
}
export interface RawPotentialHolidayStopDetail {
publicationDate: string;
credit?: number;
invoiceDate?: string;
}
export interface PotentialHolidayStopsResponse {
potentials: RawPotentialHolidayStopDetail[];
nextInvoiceDateAfterToday: string;
}
export type OutstandingHolidayStop = object; // refine type if needed
export interface OutstandingHolidayStopsResponse {
publicationsToRefund: OutstandingHolidayStop[];
}
export interface MinimalHolidayStopRequest {
id?: string;
subscriptionName?: string;
publicationsImpacted: HolidayStopDetail[];
dateRange: DateRange;
mutabilityFlags?: MutabilityFlags;
withdrawnDate?: ParsedDate;
bulkSuspensionReason?: string;
}
export interface HolidayStopRequest extends MinimalHolidayStopRequest {
mutabilityFlags: MutabilityFlags;
}
export interface GetHolidayStopsResponse {
productSpecifics: {
firstAvailableDate: Date;
issueDaysOfWeek: number[];
};
annualIssueLimit: number;
existing: HolidayStopRequest[];
}
interface RawGetHolidayStopsResponse {
issueSpecifics: Array<{
issueDayOfWeek: number;
firstAvailableDate: string;
}>;
annualIssueLimit: number;
existing: RawHolidayStopRequest[];
}
export const convertRawPotentialHolidayStopDetail = (
raw: RawPotentialHolidayStopDetail,
) => ({
estimatedPrice: raw.credit,
invoiceDate: raw.invoiceDate ? parseDate(raw.invoiceDate) : undefined,
publicationDate: parseDate(raw.publicationDate),
});
// tslint:disable-next-line:max-classes-per-file
export class PotentialHolidayStopsAsyncLoader extends AsyncLoader<PotentialHolidayStopsResponse> {}
export const getPotentialHolidayStopsFetcher =
(
subscriptionName: string,
startDate: Date,
endDate: Date,
isTestUser: boolean,
) =>
() =>
fetch(
`/api/holidays/${subscriptionName}/potential?startDate=${dateString(
startDate,
DATE_FNS_INPUT_FORMAT,
)}&endDate=${dateString(endDate, DATE_FNS_INPUT_FORMAT)}`,
{
headers: {
[MDA_TEST_USER_HEADER]: `${isTestUser}`,
},
},
);
export interface CreateOrAmendHolidayStopsResponse {
success: string;
}
// tslint:disable-next-line:max-classes-per-file
export class CreateOrAmendHolidayStopsAsyncLoader extends AsyncLoader<CreateOrAmendHolidayStopsResponse> {}
export function isHolidayStopsResponse(
data: GetHolidayStopsResponse | object | undefined,
): data is GetHolidayStopsResponse {
return !!data && data.hasOwnProperty('existing');
}
export const isNotWithdrawn = (holidayStopRequest: HolidayStopRequest) =>
!holidayStopRequest.withdrawnDate;
export const isNotBulkSuspension = (holidayStopRequest: HolidayStopRequest) =>
!holidayStopRequest.bulkSuspensionReason;
const embellishRawHolidayStop = (
rawHolidayStopRequest: RawHolidayStopRequest,
) =>
({
...rawHolidayStopRequest,
withdrawnDate: rawHolidayStopRequest.withdrawnTime
? parseDate(
rawHolidayStopRequest.withdrawnTime,
"yyyy-MM-dd'T'kk:mm:ss.SSS'Z'",
) // 2021-06-16T13:24:49.000Z
: undefined,
dateRange: dateRange(
rawHolidayStopRequest.startDate,
rawHolidayStopRequest.endDate,
),
publicationsImpacted: rawHolidayStopRequest.publicationsImpacted.map(
(raw) => ({
...raw,
publicationDate: parseDate(raw.publicationDate),
invoiceDate: raw.invoiceDate
? parseDate(raw.invoiceDate)
: undefined,
}),
),
} as HolidayStopRequest);
export const embellishExistingHolidayStops = async (response: Response) => {
const raw = (await response.json()) as RawGetHolidayStopsResponse;
return {
...raw,
productSpecifics: {
// taking the oldest date here is only knowingly safe for GW (once per week) and Voucher (no fulfilment)
// it will need to re-visited for Home Delivery
firstAvailableDate: getOldestDate(
raw.issueSpecifics.map(
(_) => parseDate(_.firstAvailableDate).date,
),
),
issueDaysOfWeek: raw.issueSpecifics.map((_) => _.issueDayOfWeek),
},
existing: raw.existing
.map(embellishRawHolidayStop)
.sort(
(a, b) =>
a.dateRange.start.valueOf() - b.dateRange.start.valueOf(),
),
} as GetHolidayStopsResponse;
};
export interface IssuesImpactedPerYear {
issuesThisYear: HolidayStopDetail[];
issuesNextYear: HolidayStopDetail[];
}
export const calculateIssuesImpactedPerYear = (
publicationsImpacted: HolidayStopDetail[],
anniversaryDate: Date,
) => {
return {
issuesThisYear: publicationsImpacted.filter(
(issue) =>
issue.publicationDate.isBefore(anniversaryDate) &&
issue.publicationDate.isSameOrAfter(
dateAddYears(anniversaryDate, -1),
),
),
issuesNextYear: publicationsImpacted.filter(
(issue) =>
issue.publicationDate.isSameOrAfter(anniversaryDate) &&
issue.publicationDate.isBefore(
dateAddYears(anniversaryDate, 1),
),
),
} as IssuesImpactedPerYear;
};
export const createGetHolidayStopsFetcher =
(subscriptionName: string, isTestUser: boolean) => () =>
fetch(`/api/holidays/${subscriptionName}`, {
headers: {
[MDA_TEST_USER_HEADER]: `${isTestUser}`,
},
});