view release on metacpan or search on metacpan
- t/model/: add '+01' to some timestamp literals because they are now timestamptz
0.305 2014-11-24 18:11 CET
- dbinit_Config.pm: notice that we already have a 'valid_intvl' trigger; revamp
'intervals' and 'locks' triggers to use it
- t/dispatch/interval_lock.t: add a group of tests to keep track of which
pathological tsranges ('intervals' in Dochazka terms) we are successfully
checking for
0.306 2014-11-26 09:31 CET
- dbinit_Config.pm: add 'intvl_not_locked' trigger before update or insert on
'intervals' table - checks to make sure the interval would not overlap with
any existing lock
- Model/Shared.pm: fix bug where 'load' routine was needlessly adding
'count => 1' to the payload, which is supposed to be just an object
- t/: some tests started to fail because we were creating intervals and locks
with the same intervals - fixed by changing the lock interval
- t/dispatch/interval_lock.t: now that we have a functioning trigger, add tests
that attempt to insert intervals that conflict with a lock
- t/model/triggers: add new subdirectory for trigger tests
- t/model/triggers/immutable_id.t: new unit to test triggers that make ID
from introducing intervals in time periods when the employee has no or
ambiguous scheduling
- t/dispatch/interval_lock.t: adapt to the above trigger (ensure testing user
has a schedule and the schedule is in effect)
- t/: disable a bunch of tests broken by introduction of this trigger
0.311 2014-11-27 23:04 CET
- dbinit_Config.pm: expand no_lock_conflict to handle DELETE as well as INSERT/UPDATE
- t/model/{interval,lock}.t: adapt to current state
- t/model/lock.t: add test confirming that it is no longer possible to delete a
locked interval
0.312 2014-11-28 11:27 CET
- t/dispatch/interval_lock.t: add some tests simulating: (1) create interval,
(2) lock it, (3) attempt to update it - interval is locked, (4) attempt to
delete it - interval is locked
- dbinit_Config.pm: add another trigger and run into problems because I didn't realize PostgreSQL
executes triggers in alphabetical order
0.313 2014-11-28 12:20 CET
- dbinit_Config.pm: rename triggers so "alphabetical order" is aligned with
"order of intended execution"
- Dispatch/{Interval,Lock,Shared}.pm: since all sanity checks will be
implemented via triggers, the sanity stuff we put here before is unnecessary,
so delete it
- t/dispatch/interval_lock.t: we are no longer returning 400 when intervals
config/sql/dbinit_Config.pm view on Meta::CPAN
this_eid := NEW.eid;
this_intvl := NEW.intvl;
ELSE
-- TG_OP = 'DELETE'
this_eid := OLD.eid;
this_intvl := OLD.intvl;
END IF;
SELECT count(*) INTO lock_count FROM locks WHERE eid=this_eid AND intvl && this_intvl;
IF lock_count > 0 THEN
RAISE EXCEPTION 'interval is locked';
END IF;
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
RETURN NEW;
ELSE
RETURN OLD;
END IF;
END;
$IMM$ LANGUAGE plpgsql/,
q/CREATE TRIGGER intvl_not_locked BEFORE INSERT OR UPDATE OR DELETE ON intervals
FOR EACH ROW EXECUTE PROCEDURE no_lock_conflict()/,
# the 'tempintvls' table and associated plumbing
q/CREATE SEQUENCE temp_intvl_seq/,
q/COMMENT ON SEQUENCE temp_intvl_seq IS 'sequence guaranteeing that each set of temporary intervals will have a unique identifier'/,
q/-- tempintvls
-- for staging fillup intervals
lib/App/Dochazka/REST/Docs/Resources.pm view on Meta::CPAN
}
=item * DELETE
Deletes the interval object whose iid is specified by the ':iid' URI parameter.
As long as the interval does not overlap with a lock interval, the delete operation
will probably work as expected.
=back
ACL note: 'active' employees can update/delete only their own unlocked intervals.
=back
=head2 C<< interval/new >>
=over
Allowed methods: POST
lib/App/Dochazka/REST/Docs/Resources.pm view on Meta::CPAN
=back
=head2 C<< lock/new >>
=over
Allowed methods: POST
This is the resource by which the attendance data entered by an employee
for a given time period can be "locked" to prevent any subsequent
modifications. It takes a request body containing, at the very least, an
C<intvl> property specifying the tsrange to lock. Additionally, administrators
can specify C<remark> and C<eid> properties.
=back
=head2 C<< lock/nick/:nick/:tsrange >>
lib/App/Dochazka/REST/Docs/Workflow.pm view on Meta::CPAN
=over
=item (a) only on their own attendance,
=item (b) only on past attendance and up to the end of the current month,
=item (c) lock interval must not overlap with any other lock intervals.
=back
=head3 Modify one's own unlocked past attendance data
Active employees can modify/delete any existing attendance data, provided the
attendance intervals in question do not conflict with any lock.
=head3 Retrieve list of non-disabled activities
Since attendance data must be associated with a valid activity, active employees
are authorized to retrieve the entire list of non-disabled activities using
a GET request on the C<activity/all> resource.
lib/App/Dochazka/REST/Guide.pm view on Meta::CPAN
=item * Privhistory (history of changes in an employee's privilege level)
=item * Schedule (a schedule)
=item * Schedhistory (history of changes in an employee's schedule)
=item * Activities (what kinds of work are recognized)
=item * Intervals (the "work", or "attendance", itself)
=item * Locks (determining whether a reporting period is locked or not)
=item * Components (Mason components, i.e. report templates)
=back
The "state" of each object is stored in a PostgreSQL database (see
L<"DATABASE"> for details).
These classes are described in the following sections.
lib/App/Dochazka/REST/Guide.pm view on Meta::CPAN
Dochazka has four privilege levels: C<admin>, C<active>, C<inactive>, and
C<passerby>:
=over
=item * C<admin> -- employee can view, modify, and place/remove locks on her
own attendance data as well as that of other employees; she can also
administer employee accounts and set privilege levels of other employees
=item * C<active> -- employee can view her own profile, attendance data,
modify her own unlocked attendance data, and place locks on her attendance
data
=item * C<inactive> -- employee can view her own profile and attendance data
=item * C<passerby> -- employee can view her own profile
=back
Dochazka's C<privhistory> object is used to track changes in an employee's
privilege level over time. Each time an employee's privilege level changes,
lib/App/Dochazka/REST/Guide.pm view on Meta::CPAN
of what she did during the interval) and a C<remark> (admin remark).
For details, see L<App::Dochazka::REST::Model::Interval>.
=head2 Lock
In Dochazka, a "lock" is a record in the "locks" table specifying that
a particular user's attendance data (i.e. activity intervals) for a
given period (tsrange) cannot be changed. That means, for intervals in
the locked tsrange:
=over
=item * existing intervals cannot be updated or deleted
=item * no new intervals can be inserted
=back
Employees can create locks (i.e., insert records into the locks table) on their
own EID, but they cannot delete or update those locks (or any others).
Administrators can insert, update, or delete locks at will.
How the lock is used will differ from site to site, and some sites may not
even use locking at all. The typical use case would be to lock all the
employee's attendance data within the given period as part of pre-payroll
processing. For example, the Dochazka client application may be set up to
enable reports to be generated only on fully locked periods.
"Fully locked" means either that a single lock record has been inserted
covering the entire period, or that the entire period is covered by multiple
locks.
Any attempts (even by administrators) to enter activity intervals that
intersect an existing lock will result in an error.
Clients can of course make it easy for the employee to lock entire blocks
of time (weeks, months, years . . .) at once, if that is deemed expedient.
For details, see L<App::Dochazka::REST::Model::Lock>.
lib/App/Dochazka/REST/Model/Lock.pm view on Meta::CPAN
=head2 Locks in the database
CREATE TABLE locks (
lid serial PRIMARY KEY,
eid integer REFERENCES Employees (EID),
intvl tsrange NOT NULL,
remark text
)
There is also a stored procedure, C<fully_locked>, that takes an EID
and a tsrange, and returns a boolean value indicating whether or not
that period is fully locked for the given employee.
=head3 Locks in the Perl API
# FIXME: MISSING VERBIAGE
=head1 EXPORTS
lib/App/Dochazka/REST/ResourceDefs.pm view on Meta::CPAN
}
=item * DELETE
Deletes the interval object whose iid is specified by the ':iid' URI parameter.
As long as the interval does not overlap with a lock interval, the delete operation
will probably work as expected.
=back
ACL note: 'active' employees can update/delete only their own unlocked intervals.
EOH
},
# /interval/new
'interval/new' =>
{
parent => 'interval',
handler => {
POST => 'handler_interval_new',
},
lib/App/Dochazka/REST/ResourceDefs.pm view on Meta::CPAN
handler => {
POST => 'handler_lock_new',
},
acl_profile => 'active',
cli => 'lock new $JSON',
description => 'Add a new attendance data lock',
documentation => <<'EOH',
=pod
This is the resource by which the attendance data entered by an employee
for a given time period can be "locked" to prevent any subsequent
modifications. It takes a request body containing, at the very least, an
C<intvl> property specifying the tsrange to lock. Additionally, administrators
can specify C<remark> and C<eid> properties.
EOH
},
# /lock/nick/:nick/:tsrange
'lock/nick/:nick/:tsrange' =>
{
parent => 'lock',
t/dispatch/interval_lock.t view on Meta::CPAN
diag( Dumper $status );
BAIL_OUT(0);
}
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
ok( $status->{'payload'} );
ok( $status->{'payload'}->{'lid'} );
my $lid = $status->payload->{'lid'};
note( "$user successfully created a lock" );
note( "let $user try to add an intervals that overlap the locked period in various ways" );
foreach my $intvl (
'[1957-01-02 08:00, 1957-01-02 12:00)', # completely within the lock interval
'[1957-01-03 23:00, 1957-01-04 01:00)', # extends past end of lock interval
'[1957-01-02 08:00, today)', # -- " -- but with 'today'
'[1956-12-31 08:00, 1957-01-02 00:05)', # starts before beginning of lock interval
) {
dbi_err( $test, 500, $user, 'POST', 'interval/new',
'{ "aid" : ' . $aid_of_work . ', "intvl" : "' . $intvl . '" }',
qr/interval is locked/i
);
}
note( "'active' can't delete locks so we have to delete them as root" );
$status = req( $test, 200, 'root', 'DELETE', "/lock/lid/$lid" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
}
note( 'as long as all required properties are present, JSON with bogus properties' );
t/dispatch/interval_lock.t view on Meta::CPAN
note( '- lock it' );
$status = req( $test, 201, 'root', 'POST', 'lock/new', <<"EOH" );
{ "intvl" : "[1957-01-01 00:00, 1957-02-01 00:00)" }
EOH
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
my $tl = $status->payload->{'lid'};
note( '- try to update it' );
dbi_err( $test, 500, 'root', 'PUT', "interval/iid/$ti",
'{ "long_desc" : "I\'m changing this interval even though it\'s locked!" }',
qr/interval is locked/ );
note( '- try to delete it' );
dbi_err( $test, 500, 'root', 'DELETE', "interval/iid/$ti", undef,
qr/interval is locked/ );
note( '- remove the lock' );
$status = req( $test, 200, 'root', 'DELETE', "lock/lid/$tl" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
note( '- now we can delete it' );
$status = req( $test, 200, 'root', 'DELETE', "interval/iid/$ti" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
t/dispatch/interval_lock.t view on Meta::CPAN
dbi_err( $test, 500, 'active', 'POST', 'interval/new',
'{ "aid" : ' . $aid_of_work . ', "eid" : ' . $eid_active .
', "intvl" : "[2014-07-31 20:00, 2014-08-01 00:00]" }', $illegal );
note( '- upper bound not evenly divisible by 5 minutes' );
dbi_err( $test, 500, 'active', 'POST', 'interval/new',
'{ "aid" : '. $aid_of_work . ', "eid" : ' . $eid_active .
', "intvl" : "[2014-07-31 20:00, 2014-08-01 00:01)" }',
qr/upper and lower bounds of interval must be evenly divisible by 5 minutes/ );
note( '- interval is locked' );
dbi_err( $test, 500, 'active', 'POST', 'interval/new',
'{ "aid" : '. $aid_of_work . ', "eid" : ' . $eid_active .
', "intvl" : "[2014-07-31 20:00, 2014-08-01 00:05)" }',
qr/interval is locked/ );
note( 'now let\'s try to attack upper bound of lock' );
note( '- this one looks like it might conflict with the lock\'s upper bound');
note( ' (2014-09-01), but since the upper bound is non-inclusive, the interval will');
note( ' be OK');
$status = req( $test, 201, 'active', 'POST', 'interval/new', <<"EOH" );
{ "aid" : $aid_of_work, "eid" : $eid_active, "intvl" : "[2014-09-01 00:00, 2014-09-01 04:00)" }
EOH
is( $status->level, 'OK' );
t/model/interval_lock.t view on Meta::CPAN
note( 'insert the lock object' );
is( noof( $dbix_conn, 'locks' ), 0 );
$status = $lock->insert( $faux_context );
is( noof( $dbix_conn, 'locks' ), 1 );
push @locks_to_delete, $lock;
note( 'attept to delete the testing interval' );
$status = $int->delete( $faux_context );
is( $status->level, 'ERR' );
is( $status->code, 'DOCHAZKA_DBI_ERR' );
like( $status->text, qr/interval is locked/ );
note( 'now test history_policy triggers:' );
note( '1. the interval runs from 08:00 - 12:00 today' );
note( '2. so attempt to insert a privhistory record effective 10:00 today' );
note( ' -- i.e., a clear policy violation' );
my $vio_ph = App::Dochazka::REST::Model::Privhistory->spawn(
eid => $emp->eid,
priv => 'passerby',
effective => "$today 10:00"
);