File Coverage

blib/lib/WebService/BambooHR.pm
Criterion Covered Total %
statement 61 91 67.0
branch 6 14 42.8
condition n/a
subroutine 12 15 80.0
pod 6 7 85.7
total 85 127 66.9


line stmt bran cond sub pod time code
1             package WebService::BambooHR;
2             $WebService::BambooHR::VERSION = '0.04';
3 5     5   1916251 use 5.006;
  5         24  
  5         233  
4 5     5   7043 use Moo;
  5         169845  
  5         36  
5 5     5   24226 use HTTP::Tiny;
  5         135222  
  5         238  
6 5     5   6238 use Try::Tiny;
  5         9017  
  5         344  
7 5     5   4853 use JSON qw(decode_json);
  5         64014  
  5         43  
8              
9             with 'WebService::BambooHR::UserAgent';
10 5     5   4575 use WebService::BambooHR::Employee;
  5         16  
  5         359  
11 5     5   3942 use WebService::BambooHR::Exception;
  5         17  
  5         219  
12 5     5   5075 use WebService::BambooHR::EmployeeChange;
  5         20  
  5         6430  
13              
14             my $DEFAULT_PHOTO_SIZE = 'small';
15             my $COMMA = ',';
16              
17             sub employee_list
18             {
19 1     1 1 950 my $self = shift;
20 1 50       11 my @fields = @_ > 0 ? @_ : $self->_field_list();
21              
22 1         11 @fields = $self->_check_fields(@fields);
23              
24 72         144 my $body = qq{\ntest\n\n}
25 1         10 .join("\n", map { qq[] } @fields)
26             .qq{\n\n\n};
27 1         13 my $response = $self->_post('reports/custom?format=json', $body);
28 1         93 my $json = $response->{content};
29              
30             # Workaround for a issues in BambooHR:
31             # - if you ask for 'status' you get back 'employeeStatus'
32             # - if you ask for field '1610' you get back '1610.0'
33 1         124 $json =~ s/"employmentStatus":/"status":/g;
34 1         268 $json =~ s/"1610.0":/"1610":/g;
35              
36 1         6395 my $report = decode_json($json);
37 1         6 return map { WebService::BambooHR::Employee->new($_); } @{ $report->{employees} };
  122         31409  
  1         8  
38             }
39              
40             sub employee_directory
41             {
42 0     0 0 0 my $self = shift;
43 0         0 my $response = $self->_get('employees/directory');
44 0         0 my $directory = decode_json($response->{content});
45              
46 0         0 return map { WebService::BambooHR::Employee->new($_); } @{ $directory->{employees} };
  0         0  
  0         0  
47             }
48              
49             sub employee_photo
50             {
51 1     1 1 854 my $self = shift;
52 1         3 my $employee_id = shift;
53 1 50       5 my $photo_size = @_ > 0 ? shift : $DEFAULT_PHOTO_SIZE;
54 1         2 my $response;
55              
56 1         2 eval {
57 1         8 $response = $self->_get("employees/$employee_id/photo/$photo_size");
58             };
59 1 50       9 if ($@) {
60 0 0       0 return undef if ($@->code == 404);
61 0         0 $@->throw();
62             };
63              
64 1         11 return $response->{content};
65             }
66              
67             sub employee
68             {
69 2     2 1 13728 my $self = shift;
70 2         4 my $employee_id = shift;
71 2 100       15 my @fields = @_ > 0 ? @_ : $self->_field_list();
72              
73 2         17 @fields = $self->_check_fields(@fields);
74              
75 1         22 my $url = "employees/$employee_id?fields=".join($COMMA, @fields);
76 1         7 my $response = $self->_get($url);
77 1         88 my $json = $response->{content};
78              
79             # Workaround for a bug in BambooHR: if you ask for 'status' you get back 'employeeStatus'
80 1         25 $json =~ s/"employmentStatus":/"status":/g;
81              
82 1         92 return WebService::BambooHR::Employee->new(decode_json($json));
83             }
84              
85             sub changed_employees
86             {
87 1     1 1 751 my $self = shift;
88 1         3 my $since = shift;
89 1         2 my $url = "employees/changed/?since=$since";
90 1         2 my @changes;
91              
92 1 50       5 if (@_ > 0) {
93 0         0 my $type = shift;
94 0         0 $url .= "&type=$type";
95             }
96 1         5 my $response = $self->_get($url);
97              
98 1         422 require XML::Simple;
99              
100             # The ForceArray and KeyAttr options make it produce more JSON like data structure
101             # see the XML::Simple doc for more
102 0           my $changed_data = XML::Simple::XMLin($response->{content}, ForceArray => 1, KeyAttr => []);
103              
104 0           return map { WebService::BambooHR::EmployeeChange->new($_) } @{ $changed_data->{employee} };
  0            
  0            
105             }
106              
107             sub add_employee
108             {
109 0     0 1   my $self = shift;
110 0           my $field_ref = shift;
111              
112 0           $self->_check_fields(keys %$field_ref);
113              
114 0           my $body = $self->_employee_record($field_ref);
115 0           my $response = $self->_post('employees/', $body);
116              
117 0           my $location = $response->{headers}->{location};
118 0 0         if ($location =~ m!/v1/employees/(\d+)$!) {
119 0           return $1;
120             } else {
121 0           my @caller = caller(0);
122              
123             # The API call appeared to work, but the response headers
124             # didn't contain the expected employee id.
125             # Is 500 really the right status code here?
126 0           WebService::BambooHR::Exception->throw({
127             method => __PACKAGE__.'::add_employee',
128             message => "API didn't return new employee id",
129             code => 500,
130             reason => 'Internal server error',
131             filename => $caller[1],
132             line_number => $caller[2],
133             });
134             }
135             }
136              
137             sub update_employee
138             {
139 0     0 1   my $self = shift;
140 0           my $id = shift;
141 0           my $field_ref = shift;
142              
143 0           $self->_check_fields(keys %$field_ref);
144              
145 0           my $body = $self->_employee_record($field_ref);
146 0           my $response = $self->_post("employees/$id", $body);
147             }
148              
149             1;
150              
151             =head1 NAME
152              
153             WebService::BambooHR - interface to the API for BambooHR.com
154              
155             =head1 SYNOPSIS
156              
157             use WebService::BambooHR;
158              
159             my $bamboo = WebService::BambooHR->new(
160             company => 'foobar',
161             api_key => '...'
162             );
163            
164             $id = $bamboo->add_employee({
165             firstName => 'Bilbo',
166             lastName => 'Baggins',
167             });
168              
169             $employee = $bamboo->employee($id);
170            
171             $bamboo->update_employee($employee->id, {
172             dateOfBirth => '1953-11-22',
173             gender => 'Male',
174             });
175              
176             =head1 DESCRIPTION
177              
178             B this is very much an alpha release. The interface is likely to change
179             from release to release. Suggestions for improving the interface are welcome.
180              
181             B provides an interface to a subset of the functionality
182             in the API for BambooHR (a commercial online HR system: lets a company manage
183             employee information, handle time off, etc).
184              
185             To talk to BambooHR you must first create an instance of this module:
186              
187             my $bamboo = WebService::BambooHR->new(
188             company => 'mycompany',
189             api_key => $api_key,
190             );
191            
192             The B field is the domain name that you use to access BambooHR.
193             For example, the above company would be accessed via C.
194             You also need an API key. See the section below on how to create one.
195              
196             Having created an instance, you can use the public methods that are described below.
197             For example, to display a list of all employees:
198              
199             @employees = $bamboo->employee_list();
200             foreach my $employee (@employees) {
201             printf "Hi %s %s\n", $employee->firstName,
202             $employee->lastName;
203             }
204              
205             Note that the C statement could more succinctly have been written as:
206              
207             print "Hi $employee\n";
208              
209             The employee class overloads string-context rendering to display
210             the employee's name.
211              
212             =head2 Generating an API key
213              
214             To get an API key you need to be an I of BambooHR.
215             Ask the owner of your BambooHR account to make you an admin user.
216             Once you are, login to Bamboo and generate an API key:
217             Look for the entry "API Keys" in the drop-menu under your name.
218              
219             =head1 METHODS
220              
221             =head2 employee_list
222              
223             Returns a list of all employees in BambooHR for your company:
224              
225             @employees = $bamboo->employee_list();
226              
227             Each employee is an instance of L.
228             See the documentation for that module to see what information is
229             available for each employee. You can find out more information
230             from the L.
231              
232             This will return both active and inactive employees:
233             check the C field if you only want to handle active employees:
234              
235             foreach my $employee ($bamboo->employee_list) {
236             next if $employee->status eq 'Inactive';
237             ...
238             }
239              
240             If you're only interested in specific employee fields, you can just ask for those:
241              
242             @fields = qw(firstName lastName workEmail);
243             @employees = $bamboo->employee_list(@fields);
244              
245             All other fields will then return C,
246             regardless of whether they're set in BambooHR.
247              
248             =head2 employee
249              
250             Used to request a single employee using the employee's internal id,
251             optionally specifying what fields should be populated from BambooHR:
252              
253             $employee = $bamboo->employee($id);
254              
255             This returns an instance of L.
256             If no fields are specified in the request, then all fields will be available
257             via attributes on the employee object.
258              
259             As for C above, you can specify just the fields you're interested in:
260              
261             @fields = qw(firstName lastName workEmail);
262             $employee = $bamboo->employee($id, @fields);
263              
264             =head2 add_employee
265              
266             Add a new employee to your company, specifying initial values
267             for as many employee fields as you want to specify.
268             You must provide B and B:
269              
270             $id = $bamboo->add_employee({
271             firstName => 'Bilbo',
272             lastName => 'Baggins',
273             jobTitle => 'Thief',
274             });
275              
276             This returns the internal id of the employee.
277              
278             =head2 update_employee
279              
280             This is used to update one or more fields for an employee:
281              
282             $bamboo->update_employee($employee_id, {
283             workEmail => 'bilbo@hobbiton.org',
284             bestEmail => 'bilbo@bag.end',
285             });
286              
287             =head2 employee_photo
288              
289             Request an employee's photo, if one has been provided:
290              
291             $photo = $bamboo->employee_photo($employee_id);
292              
293             This returns a JPEG image of size 150x150, or C
294             if the employee doesn't have a photo.
295              
296             =head2 changed_employees
297              
298             Returns a list of objects that identifies employees whose BambooHR accounts
299             have been changed since a particular date and time.
300             You must pass a date/time string in ISO 8601 format:
301              
302             @changes = $bamboo->changed_employees('2014-01-20T00:00:01Z');
303              
304             The list contains instances of class L,
305             which has three methods:
306              
307             =over 4
308              
309             =item * id
310              
311             The internal id for the employee, which you would pass to the C method,
312             for example.
313              
314             =item * action
315              
316             A string that identifies the most recent change to the employee.
317             It will be one of 'Inserted', 'Updated', or 'Deleted'.
318              
319             =item * lastChanged
320              
321             A date and time string in ISO 8601 format that gives the time of the last change.
322              
323             =back
324              
325             In place of the 'action' method, you could use one of the three convenience methods
326             (C, C, and C),
327             which are named after the legal values for action:
328              
329             print "employee was deleted\n" if $change->deleted;
330              
331             =head1 Employee objects
332              
333             A number of methods return one or more employee objects.
334             These are instances of L.
335             You can find out what fields are supported
336             from the L.
337             The methods are all named after the fields, exactly as they're given in the doc.
338             So for example:
339              
340             print "first name = ", $employee->firstName, "\n";
341             print "work email = ", $employee->workEmail, "\n";
342              
343             If you use an object in a string context, you'll get the person's full name.
344             So the following lines produce identical output:
345              
346             print "name = $employee\n";
347             print "name = ", $employee->firstName, " ", $employee->lastName, "\n";
348              
349             =head1 Exceptions
350              
351             This module throws exceptions on failure. If you don't catch these,
352             it will effectively die with an error message that identifies the
353             method being called, the line in your code, and the error that occurred.
354              
355             You can catch the exceptions using the C built-in, but you might
356             also choose to use L.
357             For example, you must have permission to get a list of employees:
358              
359             try {
360             $employee = $bamboo->employee($id);
361             } catch {
362             if ($_->code == 403) {
363             print "You don't have permission to get that employee\n";
364             } else {
365             ...
366             }
367             };
368              
369             The exceptions are instances of L.
370             Look at the documentation for that module to see what information
371             is available with each exception.
372              
373             =head1 LIMITATIONS
374              
375             The full BambooHR API is not yet supported.
376             I'll gradually fill it in as I need it, or the whim takes me.
377             Pull requests are welcome: see the github repo below.
378              
379             =head1 SEE ALSO
380              
381             L
382              
383             =head1 REPOSITORY
384              
385             L
386              
387             =head1 AUTHOR
388              
389             Neil Bowers Eneilb@cpan.orgE
390              
391             =head1 COPYRIGHT AND LICENSE
392              
393             This software is copyright (c) 2014 by Neil Bowers .
394              
395             This is free software; you can redistribute it and/or modify it under
396             the same terms as the Perl 5 programming language system itself.
397              
398             =cut
399