Skip to content

Commit

Permalink
Adds ability to submit responses along with cancelling an appointment
Browse files Browse the repository at this point in the history
  • Loading branch information
coconutcraig committed Jul 15, 2019
1 parent 891f793 commit 4cc7920
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 23 deletions.
33 changes: 33 additions & 0 deletions src/models/attendee.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import Answer from './answer';
import Attendee from './attendee';
import Response from './response';

it('can set an identifier for the attendee', async () => {
const attendee = new Attendee();

expect(attendee.as(1).getAttributes()).toEqual(
expect.objectContaining({
identifier: 1,
}),
);
});

it('can set answers when providing a single answer', async () => {
const answer = new Answer();
Expand All @@ -23,6 +34,28 @@ it('can set answers when providing multiple answers', async () => {
);
});

it('can set responses when providing a single response', async () => {
const response = new Response();
const attendee = new Attendee();

expect(attendee.responses(response).getAttributes()).toEqual(
expect.objectContaining({
responses: [response],
}),
);
});

it('can set responses when providing multiple responses', async () => {
const response = new Response();
const attendee = new Attendee();

expect(attendee.responses([response, response]).getAttributes()).toEqual(
expect.objectContaining({
responses: [response, response],
}),
);
});

it('can set location detail parameters and maintain existing attributes', async () => {
const attendee = new Attendee();
const details = {
Expand Down
52 changes: 49 additions & 3 deletions src/models/attendee.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { ModelInterface } from '../index';
import { AnswerModel } from './answer';
import Model from './model';
import { ResponseModel } from './response';

export interface AttendeeModel extends ModelInterface {
answers(answers: AnswerModel | AnswerModel[]): this;

as(identifier: number): this;

located(details: LocatableDetailParameters): this;

messagable(messageable: boolean): this;
Expand All @@ -20,6 +23,12 @@ export interface AttendeeModel extends ModelInterface {
transform(): object;
}

export interface AttendeeAttributes {
attributes?: object;
id?: number;
type: string;
}

export interface AttendeeParameters {
address?: string;
answers?: AnswerModel[] | [];
Expand All @@ -28,13 +37,15 @@ export interface AttendeeParameters {
country?: string;
email: string | null;
first_name: string | null;
identifier: number | null;
last_name: string | null;
language?: string;
messagable?: boolean;
notes?: string;
phone?: string;
postcode?: string;
region?: string;
responses?: ResponseModel[] | [];
timezone?: string;
work_phone?: string;
}
Expand Down Expand Up @@ -64,6 +75,7 @@ export default class Attendee extends Model implements AttendeeModel {
this.attributes = {
email: null,
first_name: null,
identifier: null,
last_name: null,
};
}
Expand All @@ -74,6 +86,12 @@ export default class Attendee extends Model implements AttendeeModel {
return this;
}

public as(identifier: number): this {
this.attributes.identifier = identifier;

return this;
}

public located(details: LocatableDetailParameters): this {
this.attributes = { ...this.attributes, ...details };

Expand Down Expand Up @@ -105,6 +123,12 @@ export default class Attendee extends Model implements AttendeeModel {
return this;
}

public responses(responses: ResponseModel | ResponseModel[]): this {
this.attributes.responses = Array.isArray(responses) ? responses : [responses];

return this;
}

public speaks(language: string): this {
this.attributes.language = language;

Expand All @@ -126,10 +150,23 @@ export default class Attendee extends Model implements AttendeeModel {
};
}

const responses = this.attributes.responses || [];

if (responses.length > 0) {
parameters = {
...parameters,
relationships: {
responses: {
data: (responses as ResponseModel[]).map((response: ResponseModel) => response.transform()),
},
},
};
}

return parameters;
}

protected parameters(): object {
protected parameters(): AttendeeAttributes {
const attributes: object = {
address: this.attributes.address,
cell_phone: this.attributes.cell_phone,
Expand All @@ -156,9 +193,18 @@ export default class Attendee extends Model implements AttendeeModel {
}
});

return {
attributes,
const parameters: AttendeeAttributes = {
type: 'attendees',
};

if (this.attributes.identifier) {
parameters.id = this.attributes.identifier;
}

if (Object.keys(attributes).length > 0) {
parameters.attributes = attributes;
}

return parameters;
}
}
26 changes: 20 additions & 6 deletions src/models/response.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ModelInterface } from '../index';
import Model from './model';

export interface ResponseAttributes {
form_option_id?: number;
form_question_id: number | null;
value?: string;
}

export interface ResponseModel extends ModelInterface {
for(question: number): this;

Expand Down Expand Up @@ -49,13 +55,21 @@ export default class Response extends Model implements ResponseModel {
}

public transform(): object {
const attributes: ResponseAttributes = {
form_question_id: this.attributes.question,
};

if (this.attributes.option) {
attributes.form_option_id = this.attributes.option;
}

if (this.attributes.value) {
attributes.value = this.attributes.value;
}

return {
attributes: {
form_option_id: this.attributes.option,
form_question_id: this.attributes.question,
value: this.attributes.value,
},
attributes,
type: 'responses',
};
}
}
}
54 changes: 53 additions & 1 deletion src/resources/appointment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import mockAxios from 'axios';
import Notifications from '../constants/notifications';
import Answer from '../models/answer';
import Attendee from '../models/attendee';
import Response from '../models/response';
import Appointment, { AppointmentMatcherParameters, AppointmentNotificationParameters } from './appointment';

it('can set the location property', async () => {
Expand Down Expand Up @@ -234,7 +235,58 @@ it('can cancel the given appointment for the given attendee', async () => {
await resource.cancel(1, 2);

expect(mockAxios.delete).toHaveBeenCalledTimes(1);
expect(mockAxios.delete).toHaveBeenCalledWith('appointments/1/2');
expect(mockAxios.delete).toHaveBeenCalledWith('appointments/1/2', {});
});

it('can cancel the given appointment for the given attendee while provided responses', async () => {
const resource = new Appointment(mockAxios);
const attendee = new Attendee();
const responses = [
(new Response()).for(1).is('the response'),
(new Response()).for(2).selected(1),
];

await resource
.with(
attendee.as(2).responses(responses)
)
.cancel(1, 2);

expect(mockAxios.delete).toHaveBeenCalledWith('appointments/1/2', {
data: {
relationships: {
attendees: {
data: [
{
id: 2,
relationships: {
responses: {
data: [
{
attributes: {
form_question_id: 1,
value: 'the response',
},
type: 'responses',
},
{
attributes: {
form_option_id: 1,
form_question_id: 2,
},
type: 'responses',
},
]
}
},
type: 'attendees',
}
]
}
},
type: 'appointments',
},
});
});

it('can conditionally set a filter', async () => {
Expand Down
35 changes: 22 additions & 13 deletions src/resources/appointment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface AppointmentNotificationParameters {

export interface AppointmentParameters {
data: {
attributes: {
attributes?: {
location_id: number | undefined;
service_id: number | number[] | undefined;
staff_id: number | null;
Expand Down Expand Up @@ -68,7 +68,7 @@ export interface AppointmentResource extends Resource, ConditionalResource {
}

export interface AppointmentRelationship {
attendees?: AttendeeModel[] | [];
attendees: AttendeeModel[] | [];
}

export default class Appointment extends Conditional implements AppointmentResource {
Expand All @@ -81,7 +81,9 @@ export default class Appointment extends Conditional implements AppointmentResou

this.client = client;
this.filters = {};
this.relationships = {};
this.relationships = {
attendees: [],
};
}

public at(location: number): this {
Expand All @@ -101,7 +103,7 @@ export default class Appointment extends Conditional implements AppointmentResou
}

public async cancel(appointment: number, attendee: number): Promise<any> {
return await this.client.delete(`appointments/${appointment}/${attendee}`);
return await this.client.delete(`appointments/${appointment}/${attendee}`, this.params());
}

public for(services: number | number[]): this {
Expand Down Expand Up @@ -140,7 +142,11 @@ export default class Appointment extends Conditional implements AppointmentResou
return this;
}

protected params(): AppointmentParameters {
protected params(): AppointmentParameters | object {
if (this.relationships.attendees.length === 0) {
return {};
}

const attendees = (this.relationships.attendees as AttendeeModel[]).map(
(attendee: AttendeeModel): object => {
return attendee.transform();
Expand All @@ -149,12 +155,6 @@ export default class Appointment extends Conditional implements AppointmentResou

let params: AppointmentParameters = {
data: {
attributes: {
location_id: this.filters.location,
service_id: this.filters.services,
staff_id: null,
start: this.filters.start,
},
relationships: {
attendees: {
data: attendees,
Expand All @@ -164,8 +164,17 @@ export default class Appointment extends Conditional implements AppointmentResou
},
};

if (this.filters.user) {
params.data.attributes.staff_id = this.filters.user;
if (this.filters.location || this.filters.services || this.filters.start) {
params.data.attributes = {
location_id: this.filters.location,
service_id: this.filters.services,
staff_id: null,
start: this.filters.start,
};

if (this.filters.user) {
params.data.attributes.staff_id = this.filters.user;
}
}

if (this.filters.notifications) {
Expand Down

0 comments on commit 4cc7920

Please sign in to comment.