Bump version to 0.12.0
[WWW-OrangeHRM-Client.git] / bin / orangehrm
blobfdda01ed352adcbbe718995b3c7c5567fd503b6c
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use utf8;
5 use open ':locale';
7 use WWW::OrangeHRM::Client;
8 use Term::ReadKey;
9 use User::Utmp;
10 use DateTime;
11 use File::Spec;
12 use Config::Tiny;
13 use Getopt::Long;
14 use Pod::Usage;
15 use Encode::Locale;
16 use Encode;
18 =encoding utf-8
20 =head1 NAME
22 orangehrm - Command line client for OrangeHRM
24 =head1 SYNOPSYS
26 orangehrm [OPTION...]
28 =head1 DESCRIPTION
30 Set or show time sheet in OrangeHRM system.
32 =head1 OPTIONS
34 Without any options, it will show time sheet for current month.
37 =head2 Options for configuration
39 These options can be retrieved from configuration file.
41 =over 8
43 =item B<--url URL>
45 Base URL where OrangeHRM instance is running.
47 =item B<--username USER>
49 User name to log in as. If not specified, it will be asked when needed.
51 =item B<--password PASSWORD>
53 Password to use. If not specified, it will be asked when needed.
55 =back
58 =head2 Options for showing a time sheet
60 =over 8
62 =item B<--year NUMBER>
64 Year of Gregorian calendar, default is today.
66 =item B<--month NUMBER>
68 Month of year, counts from 1, default is today.
70 =back
73 =head2 Options for filling a time sheet
75 =over 8
77 =item B<--year NUMBER>
79 Year of Gregorian calendar, default is today.
81 =item B<--month NUMBER>
83 Month of year, counts from 1, default is today.
85 =item B<--day NUMBER>
87 Day of month, counts from 1, default is today.
89 =item B<--from HH::MM>
91 =item B<--from boot>
93 =item B<--from now>
95 Begining of working time. Use C<boot> for boot time. Use C<now> for current
96 time.
98 =item B<--to HH::MM>
100 =item B<--to boot>
102 =item B<--to now>
104 End of working time. Use C<boot> for boot time. Use C<now> for current time.
106 =item B<--break HH:MM>
108 Vacant part in the working time for purpose of working break.
110 =item B<--doctor HH:MM>
112 Vacant part in the working time for purpose of medical check.
114 =item B<--comment STRING>
116 Free comment to the day.
118 =item B<--trip>
120 Specify if you are on bussines trip on this day.
122 =item B<--work>
124 Negation of B<--trip> option. Working hours are filled as at-work by default.
125 This option is usefull only to flip the trip state to work when using
126 B<--amend>.
128 =item B<--amend>
130 Partial modification. With this option, only records with explicit option will
131 be changed. By default, unspecified arguments reset corresponding records to
132 their default values.
134 =back
137 =head2 Options for submitting a time sheet
139 =over 8
141 =item B<--submit>
143 Submit current month's time sheet for review.
145 =item B<--year NUMBER>
147 Year of Gregorian calendar, default is today.
149 =item B<--month NUMBER>
151 Month of year, counts from 1, default is today.
153 =item B<--comment STRING>
155 Free comment to the submission.
157 =back
160 =head2 Options for debugging
162 =over 8
164 =item B<--debug>
166 Dump sent HTTP requests and other useful data.
168 =back
171 =head2 Other options
173 =over 8
175 =item B<--help>
177 Show program manual.
179 =item B<--version>
181 Show program version.
183 =back
185 =head1 FILES
187 =head2 F<~/.orangehrm>
189 User configuration:
191 url = https://redhat.orangehrm.com/
192 samlidp = https://saml.redhat.com/
193 samlout = https://example.redhat.com/bye
194 #username = ppisar
195 #password = Foo
197 If L<LWP::Authen::Negotiate> Perl module is installed, and a valid Kerberos
198 ticket granting ticket is available, user name and password are not needed in
199 the configuration.
201 =cut
203 # Return boot HH:MM time of day specified as argument. Otherwise return undef.
204 sub get_boot_datetime {
205 my $day = shift->clone()->truncate(to => 'day');
206 my $boot;
208 User::Utmp::utmpname(User::Utmp::WTMP_FILE);
209 for my $entry (User::Utmp::getut()) {
210 if ($entry->{'ut_type'} != User::Utmp::BOOT_TIME() or
211 !exists $entry->{'ut_time'} or !defined $entry->{'ut_time'}) {
212 next;
214 my $entry_time = DateTime->from_epoch(epoch => $entry->{'ut_time'});
215 my $entry_day = $entry_time->clone()->truncate(to => 'day');
216 if (!DateTime->compare($day, $entry_day)) {
217 if (!defined $boot or 0 > DateTime->compare($entry_time, $boot)) {
218 $boot = $entry_time;
222 if (defined $boot) {
223 $boot = sprintf('%02d:%02d', (localtime($boot->epoch))[2,1]);
225 return $boot;
229 # Callback function for passing username and password;
230 sub ask_credentials {
231 my ($prompt, $is_password) = @_;
233 print "$prompt: ";
234 if ($is_password) {
235 ReadMode('noecho');
237 my $value = ReadLine(0);
238 if ($is_password) {
239 ReadMode('restore');
240 print "\n";
242 chomp $value if (defined $value);
243 return $value;
247 my ($now, $day, $month, $year);
248 # Translate time specification by word to HH::MM. It operates on $_.
249 sub translate_time {
250 if (!defined) { return; }
251 if ($_ eq 'now') {
252 $_ = $now;
253 } elsif ($_ eq 'boot') {
254 $_ = get_boot_datetime(DateTime->new(
255 year => $year, month => $month, day => $day
257 if (!defined) {
258 die "Could not determine boot time for $year-$month-$day.\n";
263 my $config_file = File::Spec->catfile($ENV{HOME}//'/', '.orangehrm');
265 my %configuration = (
266 url => 'https://redhat.orangehrm.com/',
267 username => undef,
268 password => undef
272 # Main code
273 # Default arguments
274 my ($help, $debug, $version,
275 $from, $to, $break, $doctor, $comment, $trip, $work, $amend,
276 $submit);
277 my ($minutes, $hours);
278 ($minutes, $hours, $day, $month, $year) = (localtime)[1, 2, 3, 4, 5];
279 $month += 1;
280 $year += 1900;
281 $now = WWW::OrangeHRM::Client::normalize_time($hours . ':' . $minutes);
283 # Load configuration
284 if (-f $config_file) {
285 my $cfg = Config::Tiny->new->read($config_file);
286 if (!defined $cfg) {
287 print STDERR 'Could not parse `', $config_file,
288 ' configuration file: ', $Config::Tiny::errstr, "\n";
289 exit 1;
291 for (keys %{$cfg->{_}}) {
292 $configuration{$_} = $cfg->{_}->{$_};
296 GetOptions(
297 'url=s' => \$configuration{url},
298 'username=s' => \$configuration{username},
299 'password=s' => \$configuration{password},
300 'year=i' => \$year,
301 'month=i' => \$month,
302 'day=i' => \$day,
303 'from=s' => \$from,
304 'to=s' => \$to,
305 'break=s' => \$break,
306 'doctor=s' => \$doctor,
307 'comment=s' => \$comment,
308 'trip' => \$trip,
309 'work' => \$work,
310 'amend' => \$amend,
311 'submit' => \$submit,
312 'help' => \$help,
313 'version' => \$version,
314 'debug' => \$debug
315 ) or pod2usage(-exitstatus => 1, -verbose => 1);
316 if ($help) {
317 pod2usage(-exitstatus => 0, -verbose => 2);
319 if ($version) {
320 print $WWW::OrangeHRM::Client::VERSION, "\n";
321 exit 0;
323 if (!defined $configuration{url}) {
324 pod2usage(-exitstatus => 1, -verbose => 1,
325 -message => "Missing configuration!\n");
327 if (!$amend and (!defined $from xor !defined $to)) {
328 pod2usage(-exitstatus => 1, -verbose => 1,
329 -message => "Missing options!\n");
332 if (((defined $from or defined $to or $amend) and defined $submit)
333 or ($trip and $work)) {
334 pod2usage(-exitstatus => 1, -verbose => 1,
335 -message => "Conflicting options!\n");
338 if ($work) {
339 $trip = 0;
341 map translate_time, ($from, $to);
342 if (defined $comment) {
343 $comment = decode(locale => $comment);
347 my $automaton = WWW::Mechanize->new(
348 'env_proxy' => 1,
349 'keep_alive' => 4,
351 if ($debug) {
352 WWW::OrangeHRM::Client::debug_http($automaton);
355 # Log-in
356 if (!WWW::OrangeHRM::Client::log_in($automaton, \%configuration,
357 \&ask_credentials)) {
358 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
359 'Could not log in!');
361 print "Logged in.\n";
363 if (!WWW::OrangeHRM::Client::time_sheet($automaton)) {
364 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
365 'Could not get time sheet!');
368 my $date = WWW::OrangeHRM::Client::time_sheet_date($automaton);
369 if (!defined $date or !($date eq "$year-$month")) {
370 if (!WWW::OrangeHRM::Client::time_sheet_change($automaton, $year, $month)) {
371 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
372 'Could not change time sheet!');
376 if (defined $from or defined $to or $amend) {
377 if (!WWW::OrangeHRM::Client::time_sheet_set_day($automaton, $day,
378 $from, $to, $break, $doctor, $comment, $trip, $amend)) {
379 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
380 "Could not fill day #$day into time sheet!");
382 if (!WWW::OrangeHRM::Client::time_sheet_save($automaton)) {
383 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
384 "Could not save time sheet!");
386 } elsif (defined $submit) {
387 if (!WWW::OrangeHRM::Client::time_sheet_submit($automaton, $comment)) {
388 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
389 "Could not submit time sheet!");
391 } else {
392 if (!WWW::OrangeHRM::Client::time_sheet_show($automaton)) {
393 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
394 "Could not parse time sheet!");
398 # Log-out
399 if (!WWW::OrangeHRM::Client::log_out($automaton, \%configuration)) {
400 WWW::OrangeHRM::Client::fatal_error($automaton, $debug,
401 'Could not log out!');
403 print "Logged out.\n";
405 exit 0;
407 __END__
409 =head1 COPYRIGHT
411 Copyright © 2012, 2013, 2014, 2015, 2017, 2019 Petr Písař <ppisar@redhat.com>.
413 =head1 LICENSE
415 This is free software. You may redistribute copies of it under the terms of
416 the GNU General Public License L<http://www.gnu.org/licenses/gpl.html>.
417 There is NO WARRANTY, to the extent permitted by law.
419 =cut