File Coverage

blib/lib/App/Dochazka/REST/Model/Employee.pm
Criterion Covered Total %
statement 48 219 21.9
branch 0 70 0.0
condition 0 4 0.0
subroutine 16 37 43.2
pod 19 19 100.0
total 83 349 23.7


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2017, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33              
34             use 5.012;
35 41     41   4955 use strict;
  41         176  
36 41     41   216 use warnings;
  41         78  
  41         840  
37 41     41   195 use App::CELL qw( $CELL $log $meta $site );
  41         86  
  41         1165  
38 41     41   220 use App::Dochazka::Common qw( $today init_timepiece );
  41         64  
  41         3589  
39 41     41   3420 use App::Dochazka::REST::LDAP qw( ldap_search );
  41         49088  
  41         3155  
40 41     41   14897 use App::Dochazka::REST::Model::Shared qw(
  41         145  
  41         2387  
41 41         3211 cud
42             load
43             load_multiple
44             noof
45             priv_by_eid
46             schedule_by_eid
47             select_single
48             select_set_of_single_scalar_rows
49             );
50 41     41   10609 use Carp;
  41         357  
51 41     41   246 use Data::Dumper;
  41         81  
  41         1830  
52 41     41   216 use DBI qw(:sql_types);
  41         68  
  41         1441  
53 41     41   2678 use Params::Validate qw( :all );
  41         28115  
  41         12528  
54 41     41   278 use Try::Tiny;
  41         69  
  41         4876  
55 41     41   262  
  41         76  
  41         3609  
56             # send DBI warnings to the log
57             $SIG{__WARN__} = sub {
58             $log->notice( $_[0] );
59             };
60              
61             # we get 'spawn', 'reset', and accessors from parent
62             use parent 'App::Dochazka::Common::Model::Employee';
63 41     41   266  
  41         92  
  41         363  
64              
65              
66             =head1 NAME
67              
68             App::Dochazka::REST::Model::Employee - Employee data model
69              
70              
71              
72              
73             =head1 SYNOPSIS
74              
75             Employee data model
76              
77             use App::Dochazka::REST::Model::Employee;
78              
79             ...
80              
81              
82              
83             =head1 DESCRIPTION
84              
85             A description of the employee data model follows.
86              
87              
88             =head2 Employees in the database
89              
90             At the database level, C<App::Dochazka::REST> needs to be able to distinguish
91             one employee from another. This is accomplished by the EID. All the other
92             fields in the C<employees> table are optional.
93              
94             The C<employees> database table is defined as follows:
95              
96             CREATE TABLE IF NOT EXISTS employees (
97             eid serial PRIMARY KEY,
98             nick varchar(32) UNIQUE NOT NULL,
99             sec_id varchar(64) UNIQUE,
100             fullname varchar(96) UNIQUE,
101             email text UNIQUE,
102             passhash text,
103             salt text,
104             supervisor integer REFERENCES employees (eid),
105             remark text,
106             CONSTRAINT kosher_nick CHECK (nick ~* '^[[:alnum:]_][[:alnum:]_-]+$')
107             )
108              
109             =head3 EID
110              
111             The Employee ID (EID) is Dochazka's principal means of identifying an
112             employee. At the site, employees will be known by other means, like their
113             full name, their username, their user ID, etc. But these can and will
114             change from time to time. The EID should never, ever change.
115              
116              
117             =head3 nick
118              
119             The idea behind the C<nick> field is that each employee can have an
120             easy-to-remember nickname - ideally something that appeals to them, personally.
121             The C<nick> is required and can only contain certain characters (alphanumerics,
122             underscore, hyphen).
123              
124              
125             =head3 sec_id
126              
127             The secondary ID is an optional unique string identifying the employee.
128             This could be useful at sites where employees already have a nick (username)
129             and a numeric ID, for example. This gives administrators and supervisors the
130             ability to look up employees by their numeric ID as well as their username
131             (nick).
132              
133              
134             =head3 fullname, email
135              
136             These fields are optional. If they have a value, it must be unique. value.
137             Dochazka does not check if the email address is valid.
138              
139             Depending on how C<App::Dochazka::REST> is configured (see especially the
140             C<DOCHAZKA_PROFILE_EDITABLE_FIELDS> site parameter), these fields may be
141             read-only for employees (changeable by admins only), or the employee may be
142             allowed to maintain their own information.
143              
144              
145             =head3 passhash, salt
146              
147             The optional passhash and salt fields are designed to hold a hashed password
148             and random salt. See L<App::Dochazka::REST::Guide/AUTHENTICATION AND SESSION
149             MANAGEMENT> for details.
150              
151              
152             =head3 supervisor
153              
154             If the employee has a supervisor who will use Dochazka to monitor the
155             employee's attendance, and provided that supervisor has an EID, this field can
156             be used to set up the relationship.
157              
158              
159             =head3 remark
160              
161             This field can be used by administrators for any purpose. Ordinarily, the
162             employee herself is not permitted to edit or even display it.
163              
164              
165              
166             =head2 Employees in the Perl API
167              
168             Individual employees are represented by "employee objects". All methods and
169             functions for manipulating these objects are contained in
170             L<App::Dochazka::REST::Model::Employee>. The most important methods are:
171              
172             =over
173              
174             =item * constructor (L<spawn>)
175              
176             =item * basic accessors (L<eid>, L<sec_id>, L<nick>, L<fullname>, L<email>,
177             L<passhash>, L<salt>, L<remark>)
178              
179             =item * L<priv> (privilege "accessor" - but privilege info is not stored in
180             the object)
181              
182             =item * L<schedule> (schedule "accessor" - but schedule info is not stored
183             in the object)
184              
185             =item * L<reset> (recycles an existing object by setting it to desired state)
186              
187             =item * L<insert> (inserts object into database)
188              
189             =item * L<update> (updates database to match the object)
190              
191             =item * L<delete> (deletes record from database if nothing references it)
192              
193             =item * L<load_by_eid> (loads a single employee into the object)
194              
195             =item * L<load_by_nick> (loads a single employee into the object)
196              
197             =item * L<team_nicks> (returns list of nicks of employees whose supervisor is this employee)
198              
199             =back
200              
201             L<App::Dochazka::REST::Model::Employee> also exports some convenience
202             functions:
203              
204             =over
205              
206             =item * L<nick_exists> (given a nick, return true/false)
207              
208             =item * L<eid_exists> (given an EID, return true/false)
209              
210             =item * L<list_employees_by_priv> (given a priv level, return hash of employees with that priv level)
211              
212             =item * L<noof_employees_by_priv> (given a priv level, return number of employees with that priv level)
213              
214             =back
215              
216             For basic C<employee> object workflow, see the unit tests in
217             C<t/model/employee.t>.
218              
219              
220              
221             =head1 EXPORTS
222              
223             This module provides the following exports:
224              
225             =over
226              
227             =item L<autocreate_employee> - function
228              
229             =item L<eid_exists> - function
230              
231             =item L<get_all_sync_employees> - function
232              
233             =item L<list_employees_by_priv> - function
234              
235             =item L<nick_exists> - function
236              
237             =item L<noof_employees_by_priv> - function
238              
239             =back
240              
241             =cut
242              
243             use Exporter qw( import );
244 41     41   331732 our @EXPORT_OK = qw(
  41         91  
  41         96694  
245             autocreate_employee
246             eid_exists
247             get_all_sync_employees
248             list_employees_by_priv
249             nick_exists
250             noof_employees_by_priv
251             );
252              
253              
254              
255             =head1 METHODS
256              
257             The following functions expect to be called as methods on an employee object.
258              
259             The standard way to create an object containing an existing employee is to use
260             'load_by_eid' or 'load_by_nick':
261              
262             my $status = App::Dochazka::REST::Model::Employee->load_by_nick( 'georg' );
263             return $status unless $status->ok;
264             my $georg = $status->payload;
265             $georg->remark( 'Likes to fly kites' );
266             $status = $georg->update;
267             return $status unless $status->ok;
268              
269             ... and the like. To insert a new employee, do something like this:
270              
271             my $friedrich = App::Dochazka::REST::Model::Employee->spawn( nick => 'friedrich' );
272             my $status = $friedrich->insert;
273             return $status unless $status->ok;
274              
275             =head2 priv
276              
277             Accessor method. Wrapper for App::Dochazka::REST::Model::Shared::priv_by_eid
278             N.B.: for this method to work, the 'eid' attribute must be populated
279              
280             =cut
281              
282             my $self = shift;
283             my ( $conn, $timestamp ) = validate_pos( @_,
284 0     0 1   { isa => 'DBIx::Connector' },
285 0           { type => SCALAR, optional => 1 },
286             );
287             my $return_value = ( $timestamp )
288             ? priv_by_eid( $conn, $self->eid, $timestamp )
289 0 0         : priv_by_eid( $conn, $self->eid );
290             return if ref( $return_value );
291             return $return_value;
292 0 0         }
293 0            
294              
295             =head2 schedule
296              
297             Accessor method. Wrapper for App::Dochazka::REST::Model::Shared::schedule_by_eid
298             N.B.: for this method to work, the 'eid' attribute must be populated
299              
300             =cut
301              
302             my $self = shift;
303             my ( $conn, $timestamp ) = validate_pos( @_,
304             { isa => 'DBIx::Connector' },
305 0     0 1   { type => SCALAR, optional => 1 },
306 0           );
307             my $return_value = ( $timestamp )
308             ? schedule_by_eid( $conn, $self->eid, $timestamp )
309             : schedule_by_eid( $conn, $self->eid );
310 0 0         return if ref( $return_value );
311             return $return_value;
312             }
313 0 0          
314 0            
315             =head2 insert
316              
317             Instance method. Takes the object, as it is, and attempts to insert it into
318             the database. On success, overwrites object attributes with field values
319             actually inserted. Returns a status object.
320              
321             =cut
322              
323             my $self = shift;
324             my ( $context ) = validate_pos( @_, { type => HASHREF } );
325              
326             $self->{sync} = 0 unless defined( $self->{sync} );
327 0     0 1    
328 0           my $status = cud(
329             conn => $context->{'dbix_conn'},
330 0 0         eid => $context->{'current'}->{'eid'},
331             object => $self,
332             sql => $site->SQL_EMPLOYEE_INSERT,
333             attrs => [ 'sec_id', 'nick', 'fullname', 'email', 'passhash', 'salt',
334 0           'sync', 'supervisor', 'remark' ],
335             );
336             return $status;
337             }
338              
339              
340 0           =head2 update
341              
342             Instance method. Assuming that the object has been prepared, i.e. the EID
343             corresponds to the employee to be updated and the attributes have been
344             changed as desired, this function runs the actual UPDATE, hopefully
345             bringing the database into line with the object. Overwrites all the
346             object's attributes with the values actually written to the database.
347             Returns status object.
348              
349             =cut
350              
351             my $self = shift;
352             my ( $context ) = validate_pos( @_, { type => HASHREF } );
353              
354             return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) unless $self->{'eid'};
355              
356 0     0 1   $self->{sync} = 0 unless defined( $self->{sync} );
357 0            
358             my $status = cud(
359 0 0         conn => $context->{'dbix_conn'},
360             eid => $context->{'current'}->{'eid'},
361 0 0         object => $self,
362             sql => $site->SQL_EMPLOYEE_UPDATE_BY_EID,
363             attrs => [ 'sec_id', 'nick', 'fullname', 'email', 'passhash', 'salt',
364             'sync', 'supervisor', 'remark', 'eid' ],
365 0           );
366             return $status;
367             }
368              
369              
370             =head2 delete
371 0            
372             Instance method. Assuming the EID really corresponds to the employee to be
373             deleted, this method will execute the DELETE statement in the database. It
374             won't succeed if there are any records anywhere in the database that point
375             to this EID. Returns a status object.
376              
377             =cut
378              
379             my $self = shift;
380             my ( $context ) = validate_pos( @_, { type => HASHREF } );
381              
382             my $status = cud(
383             conn => $context->{'dbix_conn'},
384             eid => $context->{'current'}->{'eid'},
385 0     0 1   object => $self,
386 0           sql => $site->SQL_EMPLOYEE_DELETE,
387             attrs => [ 'eid' ],
388             );
389             #$self->reset( eid => $self->eid ) if $status->ok;
390 0           return $status;
391             }
392              
393              
394             =head2 ldap_sync
395              
396 0           Sync the mapping fields to the values found in the LDAP database.
397              
398             =cut
399              
400             my $self = shift;
401             $log->debug( "Entering " . __PACKAGE__ . "::sync()" );
402             die "Employee nick property not populated!" unless $self->nick =~ /\S+/;
403             my $nick = $self->nick;
404              
405             return $CELL->status_err( 'DOCHAZKA_LDAP_NOT_ENABLED' ) unless $site->DOCHAZKA_LDAP;
406             return $CELL->status_err(
407 0     0 1   'DOCHAZKA_LDAP_SYNC_PROP_FALSE',
408 0           args => [ $nick ],
409 0 0         ) unless $self->sync;
410 0           return $CELL->status_err(
411             'DOCHAZKA_LDAP_SYSTEM_USER_NOSYNC',
412 0 0         args => [ $nick ],
413 0 0         ) if grep { $nick eq $_; } @{ $site->DOCHAZKA_SYSTEM_USERS };
414              
415             $log->debug( "About to populate $nick from LDAP" );
416              
417             require Net::LDAP;
418              
419             # initiate connection to LDAP server (anonymous bind)
420 0 0         my $server = $site->DOCHAZKA_LDAP_SERVER;
  0            
  0            
421             my $ldap = Net::LDAP->new( $server );
422 0           $log->error("$@") unless $ldap;
423             return $CELL->status_err( 'Could not connect to LDAP server' ) unless $ldap;
424 0            
425             # get LDAP properties and stuff them into the employee object
426             my $count = 0;
427 0           foreach my $key ( keys( %{ $site->DOCHAZKA_LDAP_MAPPING } ) ) {
428 0           my $prop = $site->DOCHAZKA_LDAP_MAPPING->{ $key };
429 0 0         my $value = ldap_search( $ldap, $nick, $prop );
430 0 0         last unless $value;
431             $log->debug( "Setting $key to $value" );
432             $self->set( $key, $value );
433 0           $count += 1;
434 0           }
  0            
435 0            
436 0           $ldap->unbind;
437 0 0          
438 0           return $CELL->status_ok(
439 0           'DOCHAZKA_LDAP_SYNC_SUCCESS',
440 0           args => [ $count ],
441             ) unless $count < 1;
442              
443 0           return $CELL->status_not_ok( 'DOCHAZKA_LDAP_SYNC_FAILURE' );
444             }
445 0 0          
446              
447             =head2 load_by_eid
448              
449             Analogous method to L<App::Dochazka::REST::Model::Activity/"load_by_aid">.
450 0            
451             =cut
452              
453             my $self = shift;
454             my ( $conn, $eid ) = validate_pos( @_,
455             { isa => 'DBIx::Connector' },
456             { type => SCALAR },
457             # { type => SCALAR, regex => qr/^-?\d+$/ }, <-- causes a regression
458             );
459             $log->debug( "Entering " . __PACKAGE__ . "::load_by_eid with argument $eid" );
460              
461 0     0 1   return load(
462 0           conn => $conn,
463             class => __PACKAGE__,
464             sql => $site->SQL_EMPLOYEE_SELECT_BY_EID,
465             keys => [ $eid ],
466             );
467 0           }
468              
469 0            
470             =head2 load_by_nick
471              
472             Analogous method to L<App::Dochazka::REST::Model::Activity/"load_by_aid">.
473              
474             =cut
475              
476             my $self = shift;
477             my ( $conn, $nick ) = validate_pos( @_,
478             { isa => 'DBIx::Connector' },
479             { type => SCALAR },
480             );
481             $log->debug( "Entering " . __PACKAGE__ . "::load_by_nick with argument $nick" );
482              
483             return load(
484             conn => $conn,
485 0     0 1   class => __PACKAGE__,
486 0           sql => $site->SQL_EMPLOYEE_SELECT_BY_NICK,
487             keys => [ $nick ],
488             );
489             }
490 0            
491              
492 0           =head2 load_by_sec_id
493              
494             Analogous method to L<App::Dochazka::REST::Model::Activity/"load_by_aid">.
495              
496             FIXME: add unit tests
497              
498             =cut
499              
500             my $self = shift;
501             my ( $conn, $sec_id ) = validate_pos( @_,
502             { isa => 'DBIx::Connector' },
503             { type => SCALAR },
504             );
505             $log->debug( "Entering " . __PACKAGE__ . "::load_by_sec_id with argument $sec_id" );
506              
507             return load(
508             conn => $conn,
509             class => __PACKAGE__,
510 0     0 1   sql => $site->SQL_EMPLOYEE_SELECT_BY_SEC_ID,
511 0           keys => [ $sec_id ],
512             );
513             }
514              
515 0            
516             =head2 priv_change_during_range
517 0            
518             Given a DBIx::Connector object and a tsrange, returns a non-negative integer
519             value signifying the number of times the employee's priv level changed during the
520             given range.
521              
522             =cut
523              
524             my $self = shift;
525             my ( $conn, $tsr ) = validate_pos( @_,
526             { isa => 'DBIx::Connector' },
527             { type => SCALAR },
528             );
529             $log->debug( "Entering " . __PACKAGE__ . "::priv_change_during_range with argument $tsr" );
530             $log->debug( "EID is " . $self->eid );
531              
532             my $status = select_single(
533             conn => $conn,
534             sql => $site->SQL_EMPLOYEE_PRIV_CHANGE_DURING_RANGE,
535 0     0 1   keys => [ $self->eid, $tsr ],
536 0           );
537             return _privsched_change_during_range_result( "SQL_EMPLOYEE_PRIV_CHANGE_DURING_RANGE", $status );
538             }
539              
540 0           my ( $sql_stmt, $status ) = @_;
541 0           $log->debug( "Entering " . __PACKAGE__ . "::_privsched_change_during_range_result with status " .
542             Dumper $status );
543 0           # there should always be a single record, and it should be either 0 or 1
544             if ( ref( $status->payload ) ne 'ARRAY' ) {
545             die "Unexpected _privsched_change_during_range_result: status payload is not an array!";
546             }
547             my ( $plval ) = validate_pos( @{ $status->payload },
548 0           {
549             type => SCALAR,
550             callbacks => {
551             'non-negative integer' => sub { $_[0] >= 0 }
552 0     0     }
553 0           },
554             );
555             return $plval;
556 0 0         }
557 0            
558              
559 0           =head2 privhistory_at_timestamp
560              
561             Given a DBIx::Connector object and a string that must be a timestamp (or a
562             tsrange), returns an L<App::Dochazka::REST::Model::Privhistory> object
563 0     0     containing the privhistory record applicable to the employee as of the
564             timestamp (or the lower bound of the tsrange). If there is no such record, the
565             object's properties will be undefined.
566 0            
567 0           NOTE: be careful that the argument really is a valid timestamp or tsrange. If
568             it isn't valid, the DBD::Pg error will be logged and the return value will be
569             undef (not a L<App::Dochazka::REST::Model::Schedhistory> object whose
570             properties are set to undef).
571              
572             =cut
573              
574             my $self = shift;
575             my ( $conn, $arg ) = validate_pos( @_,
576             { isa => 'DBIx::Connector' },
577             { type => SCALAR },
578             );
579             $log->debug( "Entering " . __PACKAGE__ . "::privhistory_at_timestamp with argument $arg" );
580             $log->debug( "EID is " . $self->eid );
581              
582             # if it looks like a tsrange, use tsrange, otherwise use timestamp
583             my $sql = ( $arg =~ m/[[(].*,.*[])]/ )
584             ? $site->SQL_EMPLOYEE_PRIVHISTORY_AT_TSRANGE
585             : $site->SQL_EMPLOYEE_PRIVHISTORY_AT_TIMESTAMP;
586              
587 0     0 1   my $status = select_single(
588 0           conn => $conn,
589             sql => $sql,
590             keys => [ $self->eid, $arg ],
591             );
592 0           return undef unless $status->ok;
593 0            
594             $log->debug( 'privhistory_at_timestamp: database said: ' . Dumper( $status->payload ) );
595              
596 0 0         return App::Dochazka::REST::Model::Privhistory->spawn(
597             phid => $status->payload->[0],
598             eid => $status->payload->[1],
599             priv => $status->payload->[2],
600 0           effective => $status->payload->[3],
601             remark => $status->payload->[4],
602             );
603             }
604              
605 0 0          
606             =head2 schedule_change_during_range
607 0            
608             Given a DBIx::Connector object and a tsrange, returns a non-negative integer
609 0           value signifying the number of times the employee's schedule changed during the
610             given range.
611              
612             =cut
613              
614             my $self = shift;
615             my ( $conn, $tsr ) = validate_pos( @_,
616             { isa => 'DBIx::Connector' },
617             { type => SCALAR },
618             );
619             $log->debug( "Entering " . __PACKAGE__ . "::schedule_change_during_range with argument $tsr" );
620             $log->debug( "EID is " . $self->eid );
621              
622             my $status = select_single(
623             conn => $conn,
624             sql => $site->SQL_EMPLOYEE_SCHEDULE_CHANGE_DURING_RANGE,
625             keys => [ $self->eid, $tsr ],
626             );
627             return _privsched_change_during_range_result( "SQL_EMPLOYEE_SCHEDULE_CHANGE_DURING_RANGE", $status );
628 0     0 1   }
629 0            
630              
631             =head2 schedhistory_at_timestamp
632              
633 0           Given a DBIx::Connector object and a string that must be a timestamp (or a
634 0           tsrange), returns an L<App::Dochazka::REST::Model::Schedhistory> object
635             containing the history record applicable to the employee as of the timestamp
636 0           (or the lower bound of the tsrange). If there is no such record, the object's
637             properties will be undefined.
638              
639             NOTE: be careful that the argument really is a valid timestamp or tsrange. If
640             it isn't valid, the DBD::Pg error will be logged and the return value will be
641 0           undef (not a L<App::Dochazka::REST::Model::Schedhistory> object whose
642             properties are set to undef).
643              
644             =cut
645              
646             my $self = shift;
647             my ( $conn, $arg ) = validate_pos( @_,
648             { isa => 'DBIx::Connector' },
649             { type => SCALAR },
650             );
651             $log->debug( "Entering " . __PACKAGE__ . "::schedhistory_at_timestamp with argument $arg" );
652             $log->debug( "EID is " . $self->eid );
653              
654             # if it looks like a tsrange, use tsrange, otherwise use timestamp
655             my $sql = ( $arg =~ m/[[(].*,.*[])]/ )
656             ? $site->SQL_EMPLOYEE_SCHEDHISTORY_AT_TSRANGE
657             : $site->SQL_EMPLOYEE_SCHEDHISTORY_AT_TIMESTAMP;
658              
659             my $status = select_single(
660             conn => $conn,
661 0     0 1   sql => $sql,
662 0           keys => [ $self->eid, $arg ],
663             );
664             return undef unless $status->ok;
665              
666 0           $log->debug( 'schedhistory_at_timestamp: database said: ' . Dumper( $status->payload ) );
667 0            
668             return App::Dochazka::REST::Model::Schedhistory->spawn(
669             shid => $status->payload->[0],
670 0 0         eid => $status->payload->[1],
671             sid => $status->payload->[2],
672             effective => $status->payload->[3],
673             remark => $status->payload->[4],
674 0           );
675             }
676              
677              
678             =head2 team_nicks
679 0 0          
680             Given a L<DBIx::Connector> object, return a status object that, if successful,
681 0           will contain in the payload a list of employees whose supervisor is the
682             employee corresponding to C<$self>.
683 0            
684             =cut
685              
686             my $self = shift;
687             my ( $conn ) = validate_pos( @_,
688             { isa => 'DBIx::Connector' },
689             );
690             $log->debug( "Entering " . __PACKAGE__ . "::team_nicks for employee " . ( $self->nick || 'undefined' ) );
691              
692             # no EID, no team
693             return $CELL->status_ok( 'TEAM', payload => [] ) unless $self->eid;
694              
695             # if nick not populated, get it
696             $self->load_by_eid( $conn, $self->eid ) unless $self->nick =~ /\S+/;
697              
698             my $status = select_set_of_single_scalar_rows(
699             'conn' => $conn,
700             'sql' => $site->SQL_EMPLOYEE_SELECT_TEAM,
701             'keys' => [ $self->eid ],
702 0     0 1   );
703 0           return $status unless $status->ok;
704             return $CELL->status_ok(
705             'DISPATCH_LIST_EMPLOYEE_NICKS_TEAM',
706 0   0       args => [ $self->nick ],
707             payload => $status->payload,
708             );
709 0 0         }
710              
711              
712 0 0         =head2 has_reports
713              
714 0           Given a L<DBIx::Connector> object, return a status object that, if successful,
715             will contain in the payload an integer indicating how many "reports" the
716             employee has - i.e. how many employees, if any, there are whose supervisor is
717             the employee corresponding to C<$self>.
718              
719 0 0         =cut
720 0            
721             my $self = shift;
722             my ( $conn ) = validate_pos( @_,
723             { isa => 'DBIx::Connector' },
724             );
725             $log->debug( "Entering " . __PACKAGE__ . "::has_reports for employee " . ( $self->nick || 'undefined' ) );
726             my $reports;
727              
728             # no EID, no team
729             return $CELL->status_ok( 'TEAM', payload => [] ) unless $self->eid;
730              
731             my $status = select_single(
732             'conn' => $conn,
733             'sql' => $site->SQL_EMPLOYEE_HAS_REPORTS,
734             'keys' => [ $self->eid ],
735             );
736             return $status unless $status->ok;
737             ( $reports ) = @{ $status->payload };
738 0     0 1   return $CELL->status_ok(
739 0           'DISPATCH_EMPLOYEE_HAS_REPORTS_EID',
740             args => [ $self->eid ],
741             payload => $reports,
742 0   0       );
743 0           }
744              
745              
746 0 0         =head1 FUNCTIONS
747              
748 0           The following functions are not object methods.
749              
750              
751              
752             =head1 EXPORTED FUNCTIONS
753 0 0          
754 0           The following functions are exported and are not called as methods.
  0            
755 0            
756              
757             =head2 autocreate_employee
758              
759             Takes a DBIx::Connector object and a nick - the nick is assumed not to exist in
760             the Dochazka employees table. If DOCHAZKA_LDAP_AUTOCREATE is true, attempts to
761             create the employee. Returns a status object.
762              
763             =cut
764              
765             my ( $dbix_conn, $nick ) = @_;
766             $log->debug( "Entering " . __PACKAGE__ . "::autocreate_employee()" );
767             my $status;
768              
769             return $CELL->status_ok() if nick_exists( $dbix_conn, $nick );
770             return $CELL->status_not_ok( 'DOCHAZKA_NO_AUTOCREATE' ) unless $site->DOCHAZKA_LDAP_AUTOCREATE;
771              
772             my $emp = App::Dochazka::REST::Model::Employee->spawn(
773             nick => $nick,
774             sync => 1,
775             remark => 'LDAP autocreate',
776             );
777             $status = $emp->ldap_sync();
778             return $status unless $status->ok;
779              
780             my $faux_context = { 'dbix_conn' => $dbix_conn, 'current' => { 'eid' => 1 } };
781             $status = $emp->insert( $faux_context );
782             if ( $status->not_ok ) {
783 0     0 1   my $reason = $status->text;
784 0           return $CELL->status_err(
785 0           'DOCHAZKA_EMPLOYEE_CREATE_FAIL',
786             args => [ $nick, $reason ],
787 0 0         );
788 0 0         }
789             $log->notice( "Auto-created employee $nick, who was authenticated via LDAP" );
790 0            
791             my $priv = $site->DOCHAZKA_LDAP_AUTOCREATE_AS;
792             if ( $priv !~ m/^(inactive)|(active)$/ ) {
793             return $CELL->status_err(
794             'DOCHAZKA_INVALID_PARAM',
795 0           args => [ 'DOCHAZKA_LDAP_AUTOCREATE_AS', $priv ],
796 0 0         );
797             }
798 0            
799 0           # create a privhistory record (inactive/active only)
800 0 0         init_timepiece();
801 0           my $ph_obj = App::Dochazka::REST::Model::Privhistory->spawn(
802 0           eid => $emp->eid,
803             priv => $priv,
804             effective => ( $today . ' 00:00' ),
805             remark => 'LDAP autocreate',
806             );
807 0           $status = $ph_obj->insert( $faux_context );
808             if ( $status->not_ok ) {
809 0           my $reason = $status->text;
810 0 0         $status = $CELL->status_err(
811 0           'DOCHAZKA_AUTOCREATE_PRIV_PROBLEM',
812             args => [ $nick, $reason ],
813             );
814             }
815              
816             return $status;
817             }
818 0            
819 0            
820             =head2 nick_exists
821              
822             See C<exists> routine in L<App::Dochazka::REST::Model::Shared>
823              
824              
825 0           =head2 eid_exists
826 0 0          
827 0           See C<exists> routine in L<App::Dochazka::REST::Model::Shared>
828 0            
829             =cut
830              
831             BEGIN {
832             no strict 'refs';
833             *{"eid_exists"} = App::Dochazka::REST::Model::Shared::make_test_exists( 'eid' );
834 0           *{"nick_exists"} = App::Dochazka::REST::Model::Shared::make_test_exists( 'nick' );
835             }
836              
837              
838             =head2 list_employees_by_priv
839              
840             Get employee nicks. Argument can be one of the following:
841              
842             all admin active inactive passerby
843              
844             =cut
845              
846             my ( $conn, $priv ) = validate_pos( @_,
847             { isa => 'DBIx::Connector' },
848             { type => SCALAR, regex => qr/^(all)|(admin)|(active)|(inactive)|(passerby)$/ },
849             );
850 41     41   384 $log->debug( "Entering " . __PACKAGE__ . "::list_employees_by_priv with priv $priv" );
  41         85  
  41         2253  
851 41     41   278  
  41         204  
852 41         139 my $nicks = []; # reference to array of nicks
  41         19814  
853             my $sql = ''; # SQL statement
854             my $keys_arrayref = []; # reference to array of keys (may be empty)
855             if ( $priv eq 'all' ) {
856             $sql = $site->SQL_EMPLOYEE_SELECT_NICKS_ALL
857             } else {
858             $sql = $site->SQL_EMPLOYEE_SELECT_NICKS_BY_PRIV_LEVEL;
859             $keys_arrayref = [ $priv ];
860             }
861             my $status = select_set_of_single_scalar_rows(
862             'conn' => $conn,
863             'sql' => $sql,
864             'keys' => $keys_arrayref,
865 0     0 1   );
866             return $status unless $status->ok;
867            
868             return $CELL->status_ok( 'DISPATCH_LIST_EMPLOYEE_NICKS',
869 0           args => [ $priv ],
870             payload => $status->payload,
871 0           );
872 0           }
873 0            
874 0 0          
875 0           =head2 noof_employees_by_priv
876              
877 0           Get number of employees. Argument can be one of the following:
878 0            
879             total admin active inactive passerby
880 0            
881             =cut
882              
883             my ( $conn, $priv ) = validate_pos( @_,
884             { isa => 'DBIx::Connector' },
885 0 0         { type => SCALAR, regex => qr/^(total)|(admin)|(active)|(inactive)|(passerby)$/ },
886             );
887 0           $log->debug( "Entering " . __PACKAGE__ . "::noof_employees_by_priv with priv $priv" );
888              
889             $priv = lc $priv;
890              
891             if ( $priv eq 'total' ) {
892             my $count = noof( $conn, 'employees' );
893             return $CELL->status_ok(
894             'DISPATCH_COUNT_EMPLOYEES',
895             args => [ $count, $priv ],
896             payload => { count => $count } );
897             }
898              
899             return $CELL->status_err( 'DOCHAZKA_NOT_FOUND_404' ) unless
900             $priv =~ m/^(passerby)|(inactive)|(active)|(admin)$/i;
901              
902             my $sql = $site->SQL_EMPLOYEE_COUNT_BY_PRIV_LEVEL;
903 0     0 1   my ( $count ) = @{ select_single( conn => $conn, sql => $sql, keys => [ $priv ] )->payload };
904             $log->debug( "select_single returned: $count" );
905             $count += 0;
906             $CELL->status_ok( 'DISPATCH_COUNT_EMPLOYEES', args => [ $count, $priv ],
907 0           payload => { 'priv' => $priv, 'count' => $count } );
908             }
909 0            
910              
911 0 0         =head2 get_all_sync_employees
912 0            
913 0           Function returns a status object. If the status is OK, the payload will contain
914             a reference to an array of employee objects whose sync property is true.
915              
916             =cut
917              
918             my ( $conn ) = validate_pos( @_,
919 0 0         { isa => 'DBIx::Connector' },
920             );
921             return load_multiple(
922 0           conn => $conn,
923 0           class => 'App::Dochazka::REST::Model::Employee',
  0            
924 0           sql => $site->SQL_EMPLOYEE_SELECT_MULTIPLE_BY_SYNC,
925 0           keys => [ 1 ],
926 0           );
927             }
928              
929              
930             =head1 AUTHOR
931              
932             Nathan Cutler, C<< <presnypreklad@gmail.com> >>
933              
934             =cut
935              
936             1;
937