diff --git a/packages/javascript-api/src/lib/model/day-opening-hours.ts b/packages/javascript-api/src/lib/model/day-opening-hours.ts new file mode 100644 index 00000000..48a392e1 --- /dev/null +++ b/packages/javascript-api/src/lib/model/day-opening-hours.ts @@ -0,0 +1,9 @@ +import { OpeningHoursRange } from './opening-hours-range.js'; + +/** + * `businessHours` and `closed` are mutually exclusive. Neither means open all day. + */ +export type DayOpeningHours = + | { businessHours: OpeningHoursRange[]; closed?: undefined } + | { closed: true; businessHours?: undefined } + | Record; diff --git a/packages/javascript-api/src/lib/model/opening-hours-exception.ts b/packages/javascript-api/src/lib/model/opening-hours-exception.ts new file mode 100644 index 00000000..46af69d4 --- /dev/null +++ b/packages/javascript-api/src/lib/model/opening-hours-exception.ts @@ -0,0 +1,18 @@ +import { OpeningHoursRange } from './opening-hours-range.js'; + +interface OpeningHoursExceptionBase { + /** ISO date string, e.g. "2020-05-13" */ + date: string; + /** Max 30 characters */ + closedReason?: string; +} + +/** + * Requires exactly one of `closed` or `businessHours`. + */ +export type OpeningHoursException = + | (OpeningHoursExceptionBase & { closed: true; businessHours?: undefined }) + | (OpeningHoursExceptionBase & { + businessHours: OpeningHoursRange[]; + closed?: undefined; + }); diff --git a/packages/javascript-api/src/lib/model/opening-hours-range.ts b/packages/javascript-api/src/lib/model/opening-hours-range.ts new file mode 100644 index 00000000..88418ee3 --- /dev/null +++ b/packages/javascript-api/src/lib/model/opening-hours-range.ts @@ -0,0 +1,6 @@ +import { OpeningHoursTime } from './opening-hours-time.js'; + +export interface OpeningHoursRange { + opens: OpeningHoursTime; + closes: OpeningHoursTime; +} diff --git a/packages/javascript-api/src/lib/model/opening-hours-time.ts b/packages/javascript-api/src/lib/model/opening-hours-time.ts new file mode 100644 index 00000000..0c364479 --- /dev/null +++ b/packages/javascript-api/src/lib/model/opening-hours-time.ts @@ -0,0 +1,4 @@ +export interface OpeningHoursTime { + hours: number; + minutes: number; +} diff --git a/packages/javascript-api/src/lib/model/opening-hours.ts b/packages/javascript-api/src/lib/model/opening-hours.ts new file mode 100644 index 00000000..eb5c4eac --- /dev/null +++ b/packages/javascript-api/src/lib/model/opening-hours.ts @@ -0,0 +1,11 @@ +import { DayOpeningHours } from './day-opening-hours.js'; + +export interface OpeningHours { + mon: DayOpeningHours; + tue: DayOpeningHours; + wed: DayOpeningHours; + thu: DayOpeningHours; + fri: DayOpeningHours; + sat: DayOpeningHours; + sun: DayOpeningHours; +} diff --git a/packages/javascript-api/src/lib/services/location/location.service.spec.ts b/packages/javascript-api/src/lib/services/location/location.service.spec.ts index d5f3b85c..e069834c 100644 --- a/packages/javascript-api/src/lib/services/location/location.service.spec.ts +++ b/packages/javascript-api/src/lib/services/location/location.service.spec.ts @@ -147,6 +147,84 @@ describe('Location service', function () { }); }); + describe('setOpeningHours()', function () { + const OPENING_HOURS = { + mon: { + businessHours: [ + { + opens: { hours: 9, minutes: 0 }, + closes: { hours: 17, minutes: 30 }, + }, + ], + }, + tue: { + businessHours: [ + { + opens: { hours: 9, minutes: 0 }, + closes: { hours: 17, minutes: 30 }, + }, + ], + }, + wed: {}, + thu: {}, + fri: {}, + sat: { closed: true as const }, + sun: { closed: true as const }, + }; + + beforeEach(function () { + requestStub + .withArgs(`locations/${LOCATION_ID}/opening-hours`) + .resolves({}); + }); + + it('calls ApiBase.request with correct URL, method, body and headers', async function () { + await LocationService.setOpeningHours(LOCATION_ID, OPENING_HOURS); + expect( + requestStub.calledWith(`locations/${LOCATION_ID}/opening-hours`, { + method: 'PUT', + body: JSON.stringify(OPENING_HOURS), + headers: { 'X-Qminder-API-Version': '2020-09-01' }, + }), + ).toBeTruthy(); + }); + }); + + describe('setOpeningHoursExceptions()', function () { + const EXCEPTIONS = [ + { date: '2020-05-13', closed: true as const, closedReason: 'Birthday' }, + { + date: '2020-12-25', + businessHours: [ + { + opens: { hours: 10, minutes: 0 }, + closes: { hours: 14, minutes: 0 }, + }, + ], + }, + ]; + + beforeEach(function () { + requestStub + .withArgs(`locations/${LOCATION_ID}/opening-hours/exceptions`) + .resolves({}); + }); + + it('calls ApiBase.request with correct URL, method, body and headers', async function () { + await LocationService.setOpeningHoursExceptions(LOCATION_ID, EXCEPTIONS); + expect( + requestStub.calledWith( + `locations/${LOCATION_ID}/opening-hours/exceptions`, + { + method: 'PUT', + body: JSON.stringify(EXCEPTIONS), + headers: { 'X-Qminder-API-Version': '2020-09-01' }, + }, + ), + ).toBeTruthy(); + }); + }); + afterEach(function () { requestStub.restore(); }); diff --git a/packages/javascript-api/src/lib/services/location/location.service.ts b/packages/javascript-api/src/lib/services/location/location.service.ts index a18abc11..5f19e679 100644 --- a/packages/javascript-api/src/lib/services/location/location.service.ts +++ b/packages/javascript-api/src/lib/services/location/location.service.ts @@ -1,4 +1,10 @@ -import { details, getDesks, list } from './location.js'; +import { + details, + getDesks, + list, + setOpeningHours, + setOpeningHoursExceptions, +} from './location.js'; /** * The LocationService allows you to get data about Locations. @@ -74,4 +80,18 @@ export const LocationService = { * @returns a Promise that resolves to the list of desks in this location */ getDesks, + + /** + * Set the weekly opening hours for a location. + * + * Calls the following HTTP API: `PUT /locations//opening-hours` + */ + setOpeningHours, + + /** + * Set date-specific exceptions to the regular opening hours schedule. + * + * Calls the following HTTP API: `PUT /locations//opening-hours/exceptions` + */ + setOpeningHoursExceptions, }; diff --git a/packages/javascript-api/src/lib/services/location/location.ts b/packages/javascript-api/src/lib/services/location/location.ts index 313c7128..f09cac66 100644 --- a/packages/javascript-api/src/lib/services/location/location.ts +++ b/packages/javascript-api/src/lib/services/location/location.ts @@ -1,8 +1,12 @@ import { Desk } from '../../model/desk.js'; import { Location } from '../../model/location.js'; +import { OpeningHours } from '../../model/opening-hours.js'; +import { OpeningHoursException } from '../../model/opening-hours-exception.js'; import { extractId, IdOrObject } from '../../util/id-or-object.js'; import { ApiBase } from '../api-base/api-base.js'; +const V2_HEADERS = { 'X-Qminder-API-Version': '2020-09-01' } as const; + export function list(): Promise { return ApiBase.request('v1/locations/').then( (locations: { data: Location[] }) => { @@ -27,3 +31,27 @@ export function getDesks(location: IdOrObject): Promise { }, ); } + +export async function setOpeningHours( + location: IdOrObject, + openingHours: OpeningHours, +): Promise { + const locationId = extractId(location); + await ApiBase.request(`locations/${locationId}/opening-hours`, { + method: 'PUT', + body: JSON.stringify(openingHours), + headers: V2_HEADERS, + }); +} + +export async function setOpeningHoursExceptions( + location: IdOrObject, + exceptions: OpeningHoursException[], +): Promise { + const locationId = extractId(location); + await ApiBase.request(`locations/${locationId}/opening-hours/exceptions`, { + method: 'PUT', + body: JSON.stringify(exceptions), + headers: V2_HEADERS, + }); +} diff --git a/packages/javascript-api/src/public-api/model.ts b/packages/javascript-api/src/public-api/model.ts index 282b2f95..95e1f81b 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -14,6 +14,11 @@ export { TicketExtraText } from '../lib/model/ticket/ticket-extra-text.js'; export { TicketExtraUrl } from '../lib/model/ticket/ticket-extra-url.js'; export { TicketExtraOption } from '../lib/model/ticket/ticket-extra-option.js'; export { User } from '../lib/model/user.js'; +export { DayOpeningHours } from '../lib/model/day-opening-hours.js'; +export { OpeningHours } from '../lib/model/opening-hours.js'; +export { OpeningHoursException } from '../lib/model/opening-hours-exception.js'; +export { OpeningHoursRange } from '../lib/model/opening-hours-range.js'; +export { OpeningHoursTime } from '../lib/model/opening-hours-time.js'; export { Webhook } from '../lib/model/webhook.js'; export { SimpleError } from '../lib/model/errors/simple-error.js'; export { ComplexError } from '../lib/model/errors/complex-error.js';