| 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 |  |  |  |  |  |  | package App::Dochazka::REST::Model::Schedule; | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 41 |  |  | 41 |  | 4310 | use 5.012; | 
|  | 41 |  |  |  |  | 292 |  | 
| 36 | 41 |  |  | 41 |  | 271 | use strict; | 
|  | 41 |  |  |  |  | 88 |  | 
|  | 41 |  |  |  |  | 940 |  | 
| 37 | 41 |  |  | 41 |  | 254 | use warnings; | 
|  | 41 |  |  |  |  | 110 |  | 
|  | 41 |  |  |  |  | 1213 |  | 
| 38 | 41 |  |  | 41 |  | 238 | use App::CELL qw( $CELL $log $meta $site ); | 
|  | 41 |  |  |  |  | 89 |  | 
|  | 41 |  |  |  |  | 4465 |  | 
| 39 | 41 |  |  | 41 |  | 1901 | use App::Dochazka::REST::Model::Shared qw( cud decode_schedule_json load load_multiple select_single ); | 
|  | 41 |  |  |  |  | 107 |  | 
|  | 41 |  |  |  |  | 3084 |  | 
| 40 | 41 |  |  | 41 |  | 309 | use Data::Dumper; | 
|  | 41 |  |  |  |  | 154 |  | 
|  | 41 |  |  |  |  | 1880 |  | 
| 41 | 41 |  |  | 41 |  | 292 | use JSON; | 
|  | 41 |  |  |  |  | 103 |  | 
|  | 41 |  |  |  |  | 284 |  | 
| 42 | 41 |  |  | 41 |  | 4686 | use Params::Validate qw( :all ); | 
|  | 41 |  |  |  |  | 116 |  | 
|  | 41 |  |  |  |  | 6264 |  | 
| 43 | 41 |  |  | 41 |  | 385 | use Try::Tiny; | 
|  | 41 |  |  |  |  | 102 |  | 
|  | 41 |  |  |  |  | 2477 |  | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | # we get 'spawn', 'reset', and accessors from parent | 
| 46 | 41 |  |  | 41 |  | 262 | use parent 'App::Dochazka::Common::Model::Schedule'; | 
|  | 41 |  |  |  |  | 100 |  | 
|  | 41 |  |  |  |  | 278 |  | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | =head1 NAME | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | App::Dochazka::REST::Model::Schedule - schedule functions | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | use App::Dochazka::REST::Model::Schedule; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | ... | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | A description of the schedule data model follows. | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | =head2 Schedules in the database | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | =head3 Table | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | Schedules are stored the C<schedules> table. For any given schedule, there is | 
| 78 |  |  |  |  |  |  | always only one record in the table -- i.e., individual schedules can be used | 
| 79 |  |  |  |  |  |  | for multiple employees. (For example, an organization might have hundreds of | 
| 80 |  |  |  |  |  |  | employees on a single, unified schedule.) | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | CREATE TABLE IF NOT EXISTS schedules ( | 
| 83 |  |  |  |  |  |  | sid        serial PRIMARY KEY, | 
| 84 |  |  |  |  |  |  | schedule   text UNIQUE NOT NULL, | 
| 85 |  |  |  |  |  |  | disabled   boolean, | 
| 86 |  |  |  |  |  |  | remark     text | 
| 87 |  |  |  |  |  |  | ); | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | The value of the 'schedule' field is a JSON array which looks something like this: | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | [ | 
| 92 |  |  |  |  |  |  | { low_dow:"MON", low_time:"08:00", high_dow:"MON", high_time:"12:00" }, | 
| 93 |  |  |  |  |  |  | { low_dow:"MON", low_time:"12:30", high_dow:"MON", high_time:"16:30" }, | 
| 94 |  |  |  |  |  |  | { low_dow:"TUE", low_time:"08:00", high_dow:"TUE", high_time:"12:00" }, | 
| 95 |  |  |  |  |  |  | { low_dow:"TUE", low_time:"12:30", high_dow:"TUE", high_time:"16:30" }, | 
| 96 |  |  |  |  |  |  | ... | 
| 97 |  |  |  |  |  |  | ] | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | Or, to give an example of a more convoluted schedule: | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | [ | 
| 102 |  |  |  |  |  |  | { low_dow:"WED", low_time:"22:15", high_dow:"THU", high_time:"03:25" }, | 
| 103 |  |  |  |  |  |  | { low_dow:"THU", low_time:"05:25", high_dow:"THU", high_time:"09:55" }, | 
| 104 |  |  |  |  |  |  | { low_dow:"SAT", low_time:"19:05", high_dow:"SUN", high_time:"24:00" } | 
| 105 |  |  |  |  |  |  | ] | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | The intervals in the JSON string must be sorted and the whitespace, etc. | 
| 108 |  |  |  |  |  |  | must be consistent in order for the UNIQUE constraint in the 'schedule' | 
| 109 |  |  |  |  |  |  | table to work properly. However, these precautions will no longer be | 
| 110 |  |  |  |  |  |  | necessary after PostgreSQL 9.4 comes out and the field type is changed to | 
| 111 |  |  |  |  |  |  | 'jsonb'. | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | The 'disabled' field is intended go be used to control which schedules get | 
| 114 |  |  |  |  |  |  | offered in, e.g., front-end dialogs when administrators choose which schedule | 
| 115 |  |  |  |  |  |  | to assign to a new employee, and the like. For example, there may be schedules | 
| 116 |  |  |  |  |  |  | in the database that were used in the past, but it is no longer desirable to | 
| 117 |  |  |  |  |  |  | offer these schedules in the front-end dialog, so the administrator can "remove" | 
| 118 |  |  |  |  |  |  | them from the dialog by setting this field to 'true'. | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | =head3 Process for creating new schedules | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | It is important to understand how the JSON string introduced in the previous | 
| 124 |  |  |  |  |  |  | section is assembled -- or, more generally, how a schedule is created. Essentially, | 
| 125 |  |  |  |  |  |  | the schedule is first created in a C<schedintvls> table, with a record for each | 
| 126 |  |  |  |  |  |  | time interval in the schedule. This table has triggers and a C<gist> index that | 
| 127 |  |  |  |  |  |  | enforce schedule data integrity so that only a valid schedule can be inserted. | 
| 128 |  |  |  |  |  |  | Once the schedule has been successfully built up in C<schedintvls>, it is | 
| 129 |  |  |  |  |  |  | "translated" (using a stored procedure) into a single JSON string, which is | 
| 130 |  |  |  |  |  |  | stored in the C<schedules> table. This process is described in more detail below: | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | First, if the schedule already exists in the C<schedules> table, nothing | 
| 133 |  |  |  |  |  |  | more need be done -- we can skip to L<Schedhistory> | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | If the schedule we need is not yet in the database, we will have to create it. | 
| 136 |  |  |  |  |  |  | This is a three-step process: (1) build up the schedule in the C<schedintvls> | 
| 137 |  |  |  |  |  |  | table (sometimes referred to as the "scratch schedule" table because it is used | 
| 138 |  |  |  |  |  |  | to store an intermediate product with only a short lifespan); (2) translate the | 
| 139 |  |  |  |  |  |  | schedule to form the schedule's JSON representation; (3) insert the JSON string | 
| 140 |  |  |  |  |  |  | into the C<schedules> table. | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | The C<schedintvls>, or "scratch schedule", table: | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | CREATE SEQUENCE scratch_sid_seq; | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | CREATE TABLE IF NOT EXISTS schedintvls ( | 
| 147 |  |  |  |  |  |  | int_id  serial PRIMARY KEY, | 
| 148 |  |  |  |  |  |  | ssid    integer NOT NULL, | 
| 149 |  |  |  |  |  |  | intvl   tsrange NOT NULL, | 
| 150 |  |  |  |  |  |  | EXCLUDE USING gist (ssid WITH =, intvl WITH &&) | 
| 151 |  |  |  |  |  |  | )/, | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | As stated above, before the C<schedule> table is touched, a "scratch schedule" | 
| 154 |  |  |  |  |  |  | must first be created in the C<schedintvls> table. Although this operation | 
| 155 |  |  |  |  |  |  | changes the database, it should be seen as a "dry run". The C<gist> index and | 
| 156 |  |  |  |  |  |  | a trigger assure that: | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | =over | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | =item * no overlapping entries are entered | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | =item * all the entries fall within a single 168-hour period | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | =item * all the times are evenly divisible by five minutes | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | =back | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | # | 
| 169 |  |  |  |  |  |  | # FIXME: expand the trigger to check for "closed-open" C<< [ ..., ... ) >> tsrange | 
| 170 |  |  |  |  |  |  | # | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | If the schedule is successfully inserted into C<schedintvls>, the next step is | 
| 173 |  |  |  |  |  |  | to "translate", or convert, the individual intervals (expressed as tsrange | 
| 174 |  |  |  |  |  |  | values) into the four-key hashes described in L<Schedules in the database>, | 
| 175 |  |  |  |  |  |  | assemble the JSON string, and insert a new row in C<schedules>. | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | To facilitate this conversion, a stored procedure C<translate_schedintvl> was | 
| 178 |  |  |  |  |  |  | developed. | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | Successful insertion into C<schedules> will generate a Schedule ID (SID) for | 
| 181 |  |  |  |  |  |  | the schedule, enabling it to be used to make Schedhistory objects. | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | At this point, the scratch schedule is deleted from the C<schedintvls> table. | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | =head2 Schedules in the Perl API | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | =head3 L<Schedintvls> class | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | =over | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | =item * constructor (L<spawn>) | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | =item * L<reset> method (recycles an existing object) | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | =item * basic accessor (L<ssid>) | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | =item * L<intvls> accessor (arrayref containing all tsrange intervals in schedule) | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | =item * L<schedule> accessor (arrayref containing "translated" intervals) | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | =item * L<load> method (load the object from the database and translate the tsrange intervals) | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | =item * L<insert> method (insert all the tsrange elements in one go) | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | =item * L<delete> method (delete all the tsrange elements when we're done with them) | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | =item * L<json> method (generate JSON string from the translated intervals) | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | =back | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | For basic workflow, see C<t/model/schedule.t>. | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | =head3 C<Schedule> class | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | =over | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | =item * constructor (L<spawn>) | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | =item * L<reset> method (recycles an existing object) | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =item * basic accessors (L<sid>, L<schedule>, L<remark>) | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | =item * L<insert> method (inserts the schedule if it isn't in the database already) | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | =item * L<delete> method | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | =item * L<load> method (not implemented yet) | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | #=item * L<get_schedule_json> function (get JSON string associated with a given SID) | 
| 233 |  |  |  |  |  |  | # | 
| 234 |  |  |  |  |  |  | =back | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | For basic workflow, see C<t/model/schedule.t>. | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | =head1 EXPORTS | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | This module provides the following exports: | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | =over | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | #=item * C<get_schedule_json> | 
| 248 |  |  |  |  |  |  | # | 
| 249 |  |  |  |  |  |  | =item * C<get_all_schedules> | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | =item * C<sid_exists> (boolean) | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | =back | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | =cut | 
| 256 |  |  |  |  |  |  |  | 
| 257 | 41 |  |  | 41 |  | 86597 | use Exporter qw( import ); | 
|  | 41 |  |  |  |  | 118 |  | 
|  | 41 |  |  |  |  | 38123 |  | 
| 258 |  |  |  |  |  |  | our @EXPORT_OK = qw( | 
| 259 |  |  |  |  |  |  | get_all_schedules | 
| 260 |  |  |  |  |  |  | sid_exists | 
| 261 |  |  |  |  |  |  | ); | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | =head1 METHODS | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | =head2 insert | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | Instance method. Attempts to INSERT a record into the 'schedules' table. | 
| 271 |  |  |  |  |  |  | Field values are taken from the object. Returns a status object. | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | If the "schedule" field of the schedule to be inserted matches an existing | 
| 274 |  |  |  |  |  |  | schedule, no new record is inserted. Instead, the existing schedule record | 
| 275 |  |  |  |  |  |  | is returned. In such a case, the "scode", "remark", and "disabled" fields | 
| 276 |  |  |  |  |  |  | are ignored - except when they are NULL in the existing record. | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | =cut | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | sub insert { | 
| 281 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 282 | 0 |  |  |  |  |  | my ( $context ) = validate_pos( @_, { type => HASHREF } ); | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | # if the exact same schedule is already in the database, we | 
| 285 |  |  |  |  |  |  | # don't insert it again | 
| 286 |  |  |  |  |  |  | my $status = select_single( | 
| 287 |  |  |  |  |  |  | conn => $context->{'dbix_conn'}, | 
| 288 |  |  |  |  |  |  | sql => $site->SQL_SCHEDULES_SELECT_BY_SCHEDULE, | 
| 289 | 0 |  |  |  |  |  | keys => [ $self->{schedule} ], | 
| 290 |  |  |  |  |  |  | ); | 
| 291 | 0 |  |  |  |  |  | $log->info( "select_single returned: " . Dumper $status ); | 
| 292 | 0 | 0 |  |  |  |  | if ( $status->level eq 'OK' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 293 | 0 |  |  |  |  |  | my $found_sched = App::Dochazka::REST::Model::Schedule->spawn( | 
| 294 |  |  |  |  |  |  | sid => $status->payload->[0], | 
| 295 |  |  |  |  |  |  | scode => $status->payload->[1], | 
| 296 |  |  |  |  |  |  | schedule => $status->payload->[2], | 
| 297 |  |  |  |  |  |  | remark => $status->payload->[3], | 
| 298 |  |  |  |  |  |  | disabled => $status->payload->[4], | 
| 299 |  |  |  |  |  |  | ); | 
| 300 | 0 |  |  |  |  |  | $self->{'sid'} = $found_sched->sid; | 
| 301 |  |  |  |  |  |  | { | 
| 302 |  |  |  |  |  |  | # | 
| 303 |  |  |  |  |  |  | # the exact schedule exists, but if any of { scode, remark, disabled } | 
| 304 |  |  |  |  |  |  | # are NULL and we have a value, update the record to reflect the value | 
| 305 |  |  |  |  |  |  | # (in other words, do not prefer NULLs over real values) | 
| 306 |  |  |  |  |  |  | # | 
| 307 | 0 |  |  |  |  |  | my $do_update = 0; | 
|  | 0 |  |  |  |  |  |  | 
| 308 | 0 | 0 | 0 |  |  |  | if ( ! defined( $found_sched->scode ) and defined( $self->scode ) ) { | 
| 309 | 0 |  |  |  |  |  | $found_sched->scode( $self->scode ); | 
| 310 | 0 |  |  |  |  |  | $do_update = 1; | 
| 311 |  |  |  |  |  |  | } | 
| 312 | 0 | 0 | 0 |  |  |  | if ( ! defined( $found_sched->remark ) and defined( $self->remark ) ) { | 
| 313 | 0 |  |  |  |  |  | $found_sched->remark( $self->remark ); | 
| 314 | 0 |  |  |  |  |  | $do_update = 1; | 
| 315 |  |  |  |  |  |  | } | 
| 316 | 0 | 0 | 0 |  |  |  | if ( ! defined( $found_sched->disabled ) and defined( $self->disabled ) ) { | 
| 317 | 0 |  |  |  |  |  | $found_sched->disabled( $self->disabled ); | 
| 318 | 0 |  |  |  |  |  | $do_update = 1; | 
| 319 |  |  |  |  |  |  | } | 
| 320 | 0 | 0 |  |  |  |  | if ( $do_update ) { | 
| 321 | 0 |  |  |  |  |  | $status = $found_sched->update( $context ); | 
| 322 | 0 | 0 | 0 |  |  |  | if ( $status->level eq 'OK' and $status->code eq 'DOCHAZKA_CUD_OK' ) { | 
| 323 | 0 |  |  |  |  |  | $status->code( 'DOCHAZKA_SCHEDULE_UPDATE_OK' ); | 
| 324 |  |  |  |  |  |  | } | 
| 325 | 0 |  |  |  |  |  | return $status; | 
| 326 |  |  |  |  |  |  | } | 
| 327 | 0 |  |  |  |  |  | return $CELL->status_ok( 'DOCHAZKA_SCHEDULE_EXISTS', args => [ $self->{sid} ], | 
| 328 |  |  |  |  |  |  | payload => $found_sched ); | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  | } elsif( $status->level ne 'NOTICE' ) { | 
| 331 | 0 |  |  |  |  |  | $log->info( "select_single status was neither OK nor NOTICE; returning it as-is" ); | 
| 332 | 0 |  |  |  |  |  | return $status; | 
| 333 |  |  |  |  |  |  | } | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | # no exact match found, insert a new record | 
| 336 | 0 |  |  |  |  |  | $log->debug( __PACKAGE__ . "::insert calling cud to insert new schedule" ); | 
| 337 |  |  |  |  |  |  | $status = cud( | 
| 338 |  |  |  |  |  |  | conn => $context->{'dbix_conn'}, | 
| 339 | 0 |  |  |  |  |  | eid => $context->{'current'}->{'eid'}, | 
| 340 |  |  |  |  |  |  | object => $self, | 
| 341 |  |  |  |  |  |  | sql => $site->SQL_SCHEDULE_INSERT, | 
| 342 |  |  |  |  |  |  | attrs => [ 'scode', 'schedule', 'remark' ], | 
| 343 |  |  |  |  |  |  | ); | 
| 344 |  |  |  |  |  |  |  | 
| 345 | 0 | 0 |  |  |  |  | if ( $status->ok ) { | 
| 346 | 0 |  |  |  |  |  | $status->code( 'DOCHAZKA_SCHEDULE_INSERT_OK' ); | 
| 347 | 0 |  |  |  |  |  | $log->info( "Inserted new schedule with SID " . $self->{sid} ); | 
| 348 |  |  |  |  |  |  | } | 
| 349 | 0 |  |  |  |  |  | return $status; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =head2 update | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | Although we do not allow the 'sid' or 'schedule' fields to be updated, schedule | 
| 356 |  |  |  |  |  |  | records have 'scode', 'remark' and 'disabled' fields that can be updated via this | 
| 357 |  |  |  |  |  |  | method. | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | =cut | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | sub update { | 
| 362 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 363 | 0 |  |  |  |  |  | my ( $context ) = validate_pos( @_, { type => HASHREF } ); | 
| 364 |  |  |  |  |  |  |  | 
| 365 | 0 | 0 |  |  |  |  | return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) unless $self->{'sid'}; | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | my $status = cud( | 
| 368 |  |  |  |  |  |  | conn => $context->{'dbix_conn'}, | 
| 369 | 0 |  |  |  |  |  | eid => $context->{'current'}->{'eid'}, | 
| 370 |  |  |  |  |  |  | object => $self, | 
| 371 |  |  |  |  |  |  | sql => $site->SQL_SCHEDULE_UPDATE, | 
| 372 |  |  |  |  |  |  | attrs => [ 'scode', 'remark', 'disabled', 'sid' ], | 
| 373 |  |  |  |  |  |  | ); | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 0 |  |  |  |  |  | return $status; | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | =head2 delete | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | Instance method. Attempts to DELETE a schedule record. This may succeed | 
| 382 |  |  |  |  |  |  | if no other records in the database refer to this schedule. | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | =cut | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | sub delete { | 
| 387 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 388 | 0 |  |  |  |  |  | my ( $context ) = validate_pos( @_, { type => HASHREF } ); | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | my $status = cud( | 
| 391 |  |  |  |  |  |  | conn => $context->{'dbix_conn'}, | 
| 392 | 0 |  |  |  |  |  | eid => $context->{'current'}->{'eid'}, | 
| 393 |  |  |  |  |  |  | object => $self, | 
| 394 |  |  |  |  |  |  | sql => $site->SQL_SCHEDULE_DELETE, | 
| 395 |  |  |  |  |  |  | attrs => [ 'sid' ], | 
| 396 |  |  |  |  |  |  | ); | 
| 397 | 0 | 0 |  |  |  |  | $self->reset( sid => $self->{sid} ) if $status->ok; | 
| 398 |  |  |  |  |  |  |  | 
| 399 | 0 |  |  |  |  |  | $log->debug( "Entering " . __PACKAGE__ . "::delete with status " . Dumper( $status ) ); | 
| 400 | 0 |  |  |  |  |  | return $status; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | =head2 load_by_scode | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | Analogous function to L<App::Dochazka::REST::Model::Activity/"load_by_aid">. | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | =cut | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | sub load_by_scode { | 
| 411 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 412 | 0 |  |  |  |  |  | my ( $conn, $scode ) = validate_pos( @_, | 
| 413 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 414 |  |  |  |  |  |  | { type => SCALAR }, | 
| 415 |  |  |  |  |  |  | ); | 
| 416 |  |  |  |  |  |  |  | 
| 417 | 0 |  |  |  |  |  | return load( | 
| 418 |  |  |  |  |  |  | conn => $conn, | 
| 419 |  |  |  |  |  |  | class => __PACKAGE__, | 
| 420 |  |  |  |  |  |  | sql => $site->SQL_SCHEDULE_SELECT_BY_SCODE, | 
| 421 |  |  |  |  |  |  | keys => [ $scode ], | 
| 422 |  |  |  |  |  |  | ); | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | =head2 load_by_sid | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | Analogous function to L<App::Dochazka::REST::Model::Activity/"load_by_aid">. | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | =cut | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | sub load_by_sid { | 
| 434 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 435 | 0 |  |  |  |  |  | my ( $conn, $sid ) = validate_pos( @_, | 
| 436 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 437 |  |  |  |  |  |  | { type => SCALAR }, | 
| 438 |  |  |  |  |  |  | ); | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 0 |  |  |  |  |  | return load( | 
| 441 |  |  |  |  |  |  | conn => $conn, | 
| 442 |  |  |  |  |  |  | class => __PACKAGE__, | 
| 443 |  |  |  |  |  |  | sql => $site->SQL_SCHEDULE_SELECT_BY_SID, | 
| 444 |  |  |  |  |  |  | keys => [ $sid ], | 
| 445 |  |  |  |  |  |  | ); | 
| 446 |  |  |  |  |  |  | } | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | =head2 sid_exists | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | Boolean function | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | =cut | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | BEGIN { | 
| 460 | 41 |  |  | 41 |  | 386 | no strict 'refs'; | 
|  | 41 |  |  |  |  | 130 |  | 
|  | 41 |  |  |  |  | 2051 |  | 
| 461 | 41 |  |  | 41 |  | 260 | *{'sid_exists'} = App::Dochazka::REST::Model::Shared::make_test_exists( 'sid' ); | 
|  | 41 |  |  |  |  | 6259 |  | 
| 462 |  |  |  |  |  |  | } | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | =head2 get_all_schedules | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | Returns a list of all schedule objects, ordered by sid. Takes one | 
| 468 |  |  |  |  |  |  | argument - a paramhash that can contain only one key, 'disabled', | 
| 469 |  |  |  |  |  |  | which can be either true or false (defaults to true). | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | =cut | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | sub get_all_schedules { | 
| 474 | 0 |  |  | 0 | 1 |  | my %PH = validate( @_, { | 
| 475 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 476 |  |  |  |  |  |  | disabled => { type => SCALAR, default => 0 } | 
| 477 |  |  |  |  |  |  | } ); | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | my $sql = $PH{disabled} | 
| 480 | 0 | 0 |  |  |  |  | ? $site->SQL_SCHEDULES_SELECT_ALL_INCLUDING_DISABLED | 
| 481 |  |  |  |  |  |  | : $site->SQL_SCHEDULES_SELECT_ALL_EXCEPT_DISABLED; | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | # run the query and gather the results | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | return load_multiple( | 
| 486 | 0 |  |  |  |  |  | conn => $PH{'conn'}, | 
| 487 |  |  |  |  |  |  | class => __PACKAGE__, | 
| 488 |  |  |  |  |  |  | sql => $sql, | 
| 489 |  |  |  |  |  |  | keys => [], | 
| 490 |  |  |  |  |  |  | ); | 
| 491 |  |  |  |  |  |  | } | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | #=head2 get_schedule_json | 
| 495 |  |  |  |  |  |  | # | 
| 496 |  |  |  |  |  |  | #Given a SID, queries the database for the JSON string associated with the SID. | 
| 497 |  |  |  |  |  |  | #Returns undef if not found. | 
| 498 |  |  |  |  |  |  | # | 
| 499 |  |  |  |  |  |  | #=cut | 
| 500 |  |  |  |  |  |  | # | 
| 501 |  |  |  |  |  |  | #sub get_schedule_json { | 
| 502 |  |  |  |  |  |  | #    my ( $sid ) = @_; | 
| 503 |  |  |  |  |  |  | #    die "Problem with arguments in get_schedule_json" if not defined $sid; | 
| 504 |  |  |  |  |  |  | # | 
| 505 |  |  |  |  |  |  | #    my $json; | 
| 506 |  |  |  |  |  |  | #    try { | 
| 507 |  |  |  |  |  |  | #        $conn->do( fixup => sub { | 
| 508 |  |  |  |  |  |  | #            ( $json ) = $_->selectrow_array( $site->SQL_SCHEDULES_SELECT_SCHEDULE, | 
| 509 |  |  |  |  |  |  | #                                         undef, | 
| 510 |  |  |  |  |  |  | #                                         $sid ); | 
| 511 |  |  |  |  |  |  | #        } ); | 
| 512 |  |  |  |  |  |  | #    } | 
| 513 |  |  |  |  |  |  | # | 
| 514 |  |  |  |  |  |  | #    if ( $json ) { | 
| 515 |  |  |  |  |  |  | #        $log->debug( __PACKAGE__ . "::get_schedule_json got schedule from database: $json" ); | 
| 516 |  |  |  |  |  |  | #        return decode_schedule_json( $json ); | 
| 517 |  |  |  |  |  |  | #    } | 
| 518 |  |  |  |  |  |  | #    return; | 
| 519 |  |  |  |  |  |  | #} | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | =head1 AUTHOR | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | Nathan Cutler, C<< <presnypreklad@gmail.com> >> | 
| 526 |  |  |  |  |  |  |  | 
| 527 |  |  |  |  |  |  | =cut | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | 1; | 
| 530 |  |  |  |  |  |  |  |