summaryrefslogtreecommitdiff
path: root/LedgerSMB/Form.pm
blob: 5b89a64a77d4b685784354758c58d31560692988 (plain)
  1. =head1 NAME
  2. Form
  3. =head1 SYNOPSIS
  4. This module provides general legacy support functions and the central object
  5. =head1 STATUS
  6. Deprecated
  7. =head1 COPYRIGHT
  8. #====================================================================
  9. # LedgerSMB
  10. # Small Medium Business Accounting software
  11. # http://www.ledgersmb.org/
  12. #
  13. # Copyright (C) 2006
  14. # This work contains copyrighted information from a number of sources
  15. # all used with permission.
  16. #
  17. # This file contains source code included with or based on SQL-Ledger
  18. # which is Copyright Dieter Simader and DWS Systems Inc. 2000-2005
  19. # and licensed under the GNU General Public License version 2 or, at
  20. # your option, any later version. For a full list including contact
  21. # information of contributors, maintainers, and copyright holders,
  22. # see the CONTRIBUTORS file.
  23. #
  24. # Original Copyright Notice from SQL-Ledger 2.6.17 (before the fork):
  25. # Copyright (C) 2000
  26. #
  27. # Author: DWS Systems Inc.
  28. # Web: http://www.sql-ledger.org
  29. #
  30. # Contributors: Thomas Bayen <bayen@gmx.de>
  31. # Antti Kaihola <akaihola@siba.fi>
  32. # Moritz Bunkus (tex)
  33. # Jim Rawlings <jim@your-dba.com> (DB2)
  34. #====================================================================
  35. #
  36. # This file has undergone whitespace cleanup.
  37. #
  38. #====================================================================
  39. #
  40. # main package
  41. #
  42. #====================================================================
  43. =head1 METHODS
  44. =over
  45. =cut
  46. #inline documentation
  47. use Math::BigFloat lib => 'GMP';
  48. use LedgerSMB::Sysconfig;
  49. use List::Util qw(first);
  50. use LedgerSMB::Mailer;
  51. use Time::Local;
  52. use Cwd;
  53. use File::Copy;
  54. use charnames ':full';
  55. use open ':utf8';
  56. package Form;
  57. sub new {
  58. my $type = shift;
  59. my $argstr = shift;
  60. read( STDIN, $_, $ENV{CONTENT_LENGTH} );
  61. if ($argstr) {
  62. $_ = $argstr;
  63. }
  64. elsif ( $ENV{QUERY_STRING} ) {
  65. $_ = $ENV{QUERY_STRING};
  66. }
  67. elsif ( $ARGV[0] ) {
  68. $_ = $ARGV[0];
  69. }
  70. my $self = {};
  71. %$self = split /[&=]/;
  72. for ( keys %$self ) { $self->{$_} = unescape( "", $self->{$_} ) }
  73. if ( substr( $self->{action}, 0, 1 ) !~ /( |\.)/ ) {
  74. $self->{action} = lc $self->{action};
  75. $self->{action} =~ s/( |-|,|\#|\/|\.$)/_/g;
  76. $self->{nextsub} = lc $self->{nextsub};
  77. $self->{nextsub} =~ s/( |-|,|\#|\/|\.$)/_/g;
  78. }
  79. $self->{login} =~ s/[^a-zA-Z0-9._+\@'-]//g;
  80. $self->{menubar} = 1 if $self->{path} =~ /lynx/i;
  81. #menubar will be deprecated, replaced with below
  82. $self->{lynx} = 1 if $self->{path} =~ /lynx/i;
  83. $self->{version} = "SVN Trunk";
  84. $self->{dbversion} = "1.2.0";
  85. bless $self, $type;
  86. if ( $self->{path} ne 'bin/lynx' ) { $self->{path} = 'bin/mozilla'; }
  87. if ( ( $self->{script} )
  88. and not List::Util::first { $_ eq $self->{script} }
  89. @{LedgerSMB::Sysconfig::scripts} )
  90. {
  91. $self->error( 'Access Denied', __line__, __file__ );
  92. }
  93. if ( ( $self->{action} =~ /(:|')/ ) || ( $self->{nextsub} =~ /(:|')/ ) ) {
  94. $self->error( "Access Denied", __line__, __file__ );
  95. }
  96. for ( keys %$self ) { $self->{$_} =~ s/\N{NULL}//g }
  97. if ( ($self->{action} eq 'redirect') || ($self->{nextsub} eq 'redirect') ) {
  98. $self->error( "Access Denied", __line__, __file__ );
  99. }
  100. $self;
  101. }
  102. =item $form->debug([$file]);
  103. Outputs the sorted contents of $form. If a filename is specified, log to it,
  104. otherwise output to STDOUT.
  105. =cut
  106. sub debug {
  107. my ( $self, $file ) = @_;
  108. if ($file) {
  109. open( FH, '>', "$file" ) or die $!;
  110. for ( sort keys %$self ) { print FH "$_ = $self->{$_}\n" }
  111. close(FH);
  112. }
  113. else {
  114. print "\n";
  115. for ( sort keys %$self ) { print "$_ = $self->{$_}\n" }
  116. }
  117. }
  118. sub encode_all {
  119. # TODO;
  120. }
  121. sub decode_all {
  122. # TODO
  123. }
  124. =item $form->escape($str[, $beenthere]);
  125. Returns the URI-encoded $str. $beenthere is a boolean that when true forces a
  126. single encoding run. When false, it escapes the string twice if it detects
  127. that it is running on a version of Apache 2.0 earlier than 2.0.44.
  128. Note that recurring transaction support depends on this function escaping ','.
  129. =cut
  130. sub escape {
  131. my ( $self, $str, $beenthere ) = @_;
  132. # for Apache 2 we escape strings twice
  133. if ( ( $ENV{SERVER_SIGNATURE} =~ /Apache\/2\.(\d+)\.(\d+)/ )
  134. && !$beenthere )
  135. {
  136. $str = $self->escape( $str, 1 ) if $1 == 0 && $2 < 44;
  137. }
  138. utf8::encode($str);
  139. # SC: Adding commas to the ignore list will break recurring transactions
  140. $str =~ s/([^a-zA-Z0-9_.-])/sprintf("%%%02x", ord($1))/ge;
  141. $str;
  142. }
  143. =item $form->unescape($str);
  144. Returns the unencoded form of the URI-encoded $str.
  145. =cut
  146. sub unescape {
  147. my ( $self, $str ) = @_;
  148. $str =~ tr/+/ /;
  149. $str =~ s/\\$//;
  150. utf8::encode($str) if utf8::is_utf8($str);
  151. $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
  152. utf8::decode($str);
  153. $str =~ s/\r?\n/\n/g;
  154. $str;
  155. }
  156. =item $form->quote($str);
  157. Replaces all double quotes in $str with '&quot;'. Does nothing if $str is a
  158. reference.
  159. =cut
  160. sub quote {
  161. my ( $self, $str ) = @_;
  162. if ( $str && !ref($str) ) {
  163. $str =~ s/"/&quot;/g;
  164. }
  165. $str;
  166. }
  167. =item $form->unquote($str);
  168. Replaces all '&quot;' in $str with double quotes. Does nothing if $str is a
  169. reference.
  170. =cut
  171. sub unquote {
  172. my ( $self, $str ) = @_;
  173. if ( $str && !ref($str) ) {
  174. $str =~ s/&quot;/"/g;
  175. }
  176. $str;
  177. }
  178. =item $form->hide_form([...]);
  179. Outputs hidden HTML form fields to STDOUT. If values are passed into this
  180. function, only those $form values are output. If no values are passed in, all
  181. $form values are output as well as deleting $form->{header}. Values from the
  182. $form object are run through $form->quote, whereas keys/names are not.
  183. Sample output:
  184. <input type="hidden" name="login" value="testuser" />
  185. =cut
  186. sub hide_form {
  187. my $self = shift;
  188. if (@_) {
  189. for (@_) {
  190. print qq|<input type="hidden" name="$_" value="|
  191. . $self->quote( $self->{$_} )
  192. . qq|" />\n|;
  193. }
  194. }
  195. else {
  196. delete $self->{header};
  197. for ( sort keys %$self ) {
  198. print qq|<input type="hidden" name="$_" value="|
  199. . $self->quote( $self->{$_} )
  200. . qq|" />\n|;
  201. }
  202. }
  203. }
  204. =item $form->error($msg);
  205. Output an error message, $msg. If a CGI environment is detected, this outputs
  206. an HTTP and HTML header section if required, and displays the message after
  207. running it through $form->format_string. If it is not a CGI environment and
  208. $ENV{error_function} is set, call the specified function with $msg as the sole
  209. argument. Otherwise, this function simply dies with $msg.
  210. This function does not return. Execution is terminated at the end of the
  211. appropriate path.
  212. =cut
  213. sub error {
  214. my ( $self, $msg ) = @_;
  215. if ( $ENV{GATEWAY_INTERFACE} ) {
  216. $self->{msg} = $msg;
  217. $self->{format} = "html";
  218. $self->format_string('msg');
  219. delete $self->{pre};
  220. if ( !$self->{header} ) {
  221. $self->header;
  222. }
  223. print
  224. qq|<body><h2 class="error">Error!</h2> <p><b>$self->{msg}</b></body>|;
  225. exit;
  226. }
  227. else {
  228. if ( $ENV{error_function} ) {
  229. &{ $ENV{error_function} }($msg);
  230. }
  231. die "Error: $msg\n";
  232. }
  233. }
  234. =item $form->info($msg);
  235. Output an informational message, $msg. If a CGI environment is detected, this
  236. outputs an HTTP and HTML header section if required, and displays the message
  237. in bold tags without escaping. If it is not a CGI environment and
  238. $ENV{info_function} is set, call the specified function with $msg as the sole
  239. argument. Otherwise, this function simply prints $msg to STDOUT.
  240. =cut
  241. sub info {
  242. my ( $self, $msg ) = @_;
  243. if ( $ENV{GATEWAY_INTERFACE} ) {
  244. $msg =~ s/\n/<br>/g;
  245. delete $self->{pre};
  246. if ( !$self->{header} ) {
  247. $self->header;
  248. print qq| <body>|;
  249. $self->{header} = 1;
  250. }
  251. print "<b>$msg</b>";
  252. }
  253. else {
  254. if ( $ENV{info_function} ) {
  255. &{ $ENV{info_function} }($msg);
  256. }
  257. else {
  258. print "$msg\n";
  259. }
  260. }
  261. }
  262. =item $form->numtextrows($str, $cols[, $maxrows]);
  263. Returns the number of rows of $cols columns can be formed by $str. If $maxrows
  264. is set and the number of rows is greater than $maxrows, this returns $maxrows.
  265. In the determination of rowcount, newline characters, "\n", are taken into
  266. account while spaces are not.
  267. =cut
  268. sub numtextrows {
  269. my ( $self, $str, $cols, $maxrows ) = @_;
  270. my $rows = 0;
  271. for ( split /\n/, $str ) {
  272. $rows += int( ( (length) - 2 ) / $cols ) + 1;
  273. }
  274. $maxrows = $rows unless defined $maxrows;
  275. return ( $rows > $maxrows ) ? $maxrows : $rows;
  276. }
  277. =item $form->dberror($msg);
  278. Outputs a message as in $form->error but with $DBI::errstr automatically
  279. appended to $msg.
  280. =cut
  281. sub dberror {
  282. my ( $self, $msg ) = @_;
  283. $self->error( "$msg\n" . $DBI::errstr );
  284. }
  285. =item $form->isblank($name, $msg);
  286. Calls $form->error($msg) if the value of $form->{$name} matches /^\s*$/.
  287. =cut
  288. sub isblank {
  289. my ( $self, $name, $msg ) = @_;
  290. $self->error($msg) if $self->{$name} =~ /^\s*$/;
  291. }
  292. =item $form->header([$init, $headeradd]);
  293. Outputs HTML and HTTP headers and sets $form->{header} to indicate that headers
  294. have been output. If called with $form->{header} set or in a non-CGI
  295. environment, does not output anything. $init is ignored. $headeradd is data
  296. to be added to the <head> portion of the output headers. $form->{stylesheet},
  297. $form->{title}, $form->{titlebar}, and $form->{pre} all affect the output of
  298. this function.
  299. If the stylesheet indicated by $form->{stylesheet} exists, output a link tag
  300. to reference it. If $form->{title} is false, the title text is the value of
  301. $form->{titlebar}. If $form->{title} is true, the title text takes the form of
  302. "$form->{title} - $form->{titlebar}". The value of $form->{pre} is output
  303. immediately after the closing of <head>.
  304. =cut
  305. sub header {
  306. my ( $self, $init, $headeradd ) = @_;
  307. return if $self->{header};
  308. my ( $stylesheet, $favicon, $charset );
  309. if ( $ENV{GATEWAY_INTERFACE} ) {
  310. if ( $self->{stylesheet} && ( -f "css/$self->{stylesheet}" ) ) {
  311. $stylesheet =
  312. qq|<link rel="stylesheet" href="css/$self->{stylesheet}" type="text/css" title="LedgerSMB stylesheet" />\n|;
  313. }
  314. $self->{charset} ||= "utf-8";
  315. $charset =
  316. qq|<meta http-equiv="content-type" content="text/html; charset=$self->{charset}" />\n|;
  317. $self->{titlebar} =
  318. ( $self->{title} )
  319. ? "$self->{title} - $self->{titlebar}"
  320. : $self->{titlebar};
  321. print qq|Content-Type: text/html; charset=utf-8\n\n
  322. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  323. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  324. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  325. <head>
  326. <title>$self->{titlebar}</title>
  327. <meta http-equiv="Pragma" content="no-cache" />
  328. <meta http-equiv="Expires" content="-1" />
  329. <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
  330. $stylesheet
  331. $charset
  332. <meta name="robots" content="noindex,nofollow" />
  333. $headeradd
  334. </head>
  335. $self->{pre} \n|;
  336. }
  337. $self->{header} = 1;
  338. }
  339. =item $form->redirect([$msg]);
  340. If $form->{callback} is set or $msg is not set, call the redirect function in
  341. common.pl. If main::redirect returns, exit.
  342. Otherwise, output $msg as an informational message with $form->info($msg).
  343. =cut
  344. sub redirect {
  345. my ( $self, $msg ) = @_;
  346. if ( $self->{callback} || !$msg ) {
  347. main::redirect();
  348. exit;
  349. }
  350. else {
  351. $self->info($msg);
  352. }
  353. }
  354. =item $form->sort_columns(@columns);
  355. Sorts the list @columns. If $form->{sort} is unset, do nothing. If the value
  356. of $form->{sort} does not exist in @columns, returns the list formed by the
  357. value of $form->{sort} followed by the values of @columns. If the value of
  358. $form->{sort} is in @columns, return the list formed by @columns with the value
  359. of $form->{sort} moved to the head of the list.
  360. =cut
  361. sub sort_columns {
  362. my ( $self, @columns ) = @_;
  363. if ( $self->{sort} ) {
  364. if (@columns) {
  365. @columns = grep !/^$self->{sort}$/, @columns;
  366. splice @columns, 0, 0, $self->{sort};
  367. }
  368. }
  369. @columns;
  370. }
  371. =item $form->sort_order($columns[, $ordinal]);
  372. Returns a string that contains ordering details for the columns in SQL form.
  373. $columns is a reference to a list of columns, $ordinal is a reference to a hash
  374. that maps column names to ordinal positions. This function depends upon the
  375. values of $form->{direction}, $form->{sort}, and $form->{oldsort}.
  376. If $form->{direction} is false, it becomes 'ASC'. If $form->{direction} is true
  377. and $form->{sort} and $form->{oldsort} are equal, reverse the order specified by
  378. $form->{direction}. $form->{oldsort} is set to the same value as $form->{sort}
  379. The actual sorting of $columns happens as in $form->sort_columns(@$columns).
  380. If $ordinal is set, the positions given by it are substituted for the names of
  381. columns returned.
  382. =cut
  383. sub sort_order {
  384. my ( $self, $columns, $ordinal ) = @_;
  385. # setup direction
  386. if ( $self->{direction} ) {
  387. if ( $self->{sort} eq $self->{oldsort} ) {
  388. if ( $self->{direction} eq 'ASC' ) {
  389. $self->{direction} = "DESC";
  390. }
  391. else {
  392. $self->{direction} = "ASC";
  393. }
  394. }
  395. }
  396. else {
  397. $self->{direction} = "ASC";
  398. }
  399. $self->{oldsort} = $self->{sort};
  400. my @a = $self->sort_columns( @{$columns} );
  401. if (%$ordinal) {
  402. $a[0] =
  403. ( $ordinal->{ $a[$_] } )
  404. ? "$ordinal->{$a[0]} $self->{direction}"
  405. : "$a[0] $self->{direction}";
  406. for ( 1 .. $#a ) {
  407. $a[$_] = $ordinal->{ $a[$_] } if $ordinal->{ $a[$_] };
  408. }
  409. }
  410. else {
  411. $a[0] .= " $self->{direction}";
  412. }
  413. $sortorder = join ',', @a;
  414. $sortorder;
  415. }
  416. =item $form->format_amount($myconfig, $amount, $places, $dash);
  417. Returns $amount as formatted in the form specified by $form->{numberformat}.
  418. $places is the number of decimal places to have in the output. $dash indicates
  419. how to represent conditions surrounding values.
  420. +-------+----------+---------+------+
  421. | $dash | -1.00 | 1.00 | 0.00 |
  422. +-------+----------+---------+------+
  423. | - | (1.00) | 1.00 | - |
  424. | DRCR | 1.00 DR | 1.00 CR | DRCR |
  425. | 0 | -1.00 | 1.00 | 0.00 |
  426. | x | -1.00 | 1.00 | x |
  427. | undef | -1.00 | 1.00 | |
  428. +-------+----------+---------+------+
  429. Sample behaviour of the formatted output of various numbers for select $dash
  430. values.
  431. =cut
  432. sub format_amount {
  433. my ( $self, $myconfig, $amount, $places, $dash ) = @_;
  434. my $negative;
  435. if ($amount) {
  436. $amount = $self->parse_amount( $myconfig, $amount );
  437. $negative = ( $amount < 0 );
  438. $amount =~ s/-//;
  439. }
  440. if ( $places =~ /\d+/ ) {
  441. #$places = 4 if $places == 2;
  442. $amount = $self->round_amount( $amount, $places );
  443. }
  444. # is the amount negative
  445. # Parse $myconfig->{numberformat}
  446. my ( $ts, $ds ) = ( $1, $2 );
  447. if ($amount) {
  448. if ( $myconfig->{numberformat} ) {
  449. my ( $whole, $dec ) = split /\./, "$amount";
  450. $amount = join '', reverse split //, $whole;
  451. if ($places) {
  452. $dec .= "0" x $places;
  453. $dec = substr( $dec, 0, $places );
  454. }
  455. if ( $myconfig->{numberformat} eq '1,000.00' ) {
  456. $amount =~ s/\d{3,}?/$&,/g;
  457. $amount =~ s/,$//;
  458. $amount = join '', reverse split //, $amount;
  459. $amount .= "\.$dec" if ( $dec ne "" );
  460. }
  461. elsif ( $myconfig->{numberformat} eq '1 000.00' ) {
  462. $amount =~ s/\d{3,}?/$& /g;
  463. $amount =~ s/\s$//;
  464. $amount = join '', reverse split //, $amount;
  465. $amount .= "\.$dec" if ( $dec ne "" );
  466. }
  467. elsif ( $myconfig->{numberformat} eq "1'000.00" ) {
  468. $amount =~ s/\d{3,}?/$&'/g;
  469. $amount =~ s/'$//;
  470. $amount = join '', reverse split //, $amount;
  471. $amount .= "\.$dec" if ( $dec ne "" );
  472. }
  473. elsif ( $myconfig->{numberformat} eq '1.000,00' ) {
  474. $amount =~ s/\d{3,}?/$&./g;
  475. $amount =~ s/\.$//;
  476. $amount = join '', reverse split //, $amount;
  477. $amount .= ",$dec" if ( $dec ne "" );
  478. }
  479. elsif ( $myconfig->{numberformat} eq '1000,00' ) {
  480. $amount = "$whole";
  481. $amount .= ",$dec" if ( $dec ne "" );
  482. }
  483. elsif ( $myconfig->{numberformat} eq '1000.00' ) {
  484. $amount = "$whole";
  485. $amount .= ".$dec" if ( $dec ne "" );
  486. }
  487. if ( $dash =~ /-/ ) {
  488. $amount = ($negative) ? "($amount)" : "$amount";
  489. }
  490. elsif ( $dash =~ /DRCR/ ) {
  491. $amount = ($negative) ? "$amount DR" : "$amount CR";
  492. }
  493. else {
  494. $amount = ($negative) ? "-$amount" : "$amount";
  495. }
  496. }
  497. }
  498. else {
  499. if ( $dash eq "0" && $places ) {
  500. if ( $myconfig->{numberformat} =~ /0,00$/ ) {
  501. $amount = "0" . "," . "0" x $places;
  502. }
  503. else {
  504. $amount = "0" . "." . "0" x $places;
  505. }
  506. }
  507. else {
  508. $amount = ( $dash ne "" ) ? "$dash" : "";
  509. }
  510. }
  511. $amount;
  512. }
  513. =item $form->parse_amount($myconfig, $amount);
  514. Return a Math::BigFloat containing the value of $amount where $amount is
  515. formatted as $myconfig->{numberformat}. If $amount is '' or undefined, it is
  516. treated as zero. DRCR and parenthesis notation is accepted in addition to
  517. negative sign notation.
  518. =cut
  519. sub parse_amount {
  520. my ( $self, $myconfig, $amount ) = @_;
  521. if ( ( $amount eq '' ) or ( ! defined $amount ) ) {
  522. $amount = 0;
  523. }
  524. if ( UNIVERSAL::isa( $amount, 'Math::BigFloat' ) )
  525. { # Amount may not be an object
  526. return $amount;
  527. }
  528. my $numberformat = $myconfig->{numberformat};
  529. if ( ( $numberformat eq '1.000,00' )
  530. || ( $numberformat eq '1000,00' ) )
  531. {
  532. $amount =~ s/\.//g;
  533. $amount =~ s/,/./;
  534. }
  535. elsif ( $numberformat eq '1 000.00' ) {
  536. $amount =~ s/\s//g;
  537. }
  538. elsif ( $numberformat eq "1'000.00" ) {
  539. $amount =~ s/'//g;
  540. }
  541. $amount =~ s/,//g;
  542. if ( $amount =~ s/\((\d*\.?\d*)\)/$1/ ) {
  543. $amount = $1 * -1;
  544. }
  545. elsif ( $amount =~ s/(\d*\.?\d*)\s?DR/$1/ ) {
  546. $amount = $1 * -1;
  547. }
  548. $amount =~ s/\s?CR//;
  549. $amount =~ /(\d*)\.(\d*)/;
  550. my $decimalplaces = length $1 + length $2;
  551. $amount = new Math::BigFloat($amount);
  552. return ( $amount * 1 );
  553. }
  554. =item $form->round_amount($amount, $places);
  555. Rounds the provided $amount to $places decimal places.
  556. =cut
  557. sub round_amount {
  558. my ( $self, $amount, $places ) = @_;
  559. # These rounding rules follow from the previous implementation.
  560. # They should be changed to allow different rules for different accounts.
  561. Math::BigFloat->round_mode('+inf') if $amount >= 0;
  562. Math::BigFloat->round_mode('-inf') if $amount < 0;
  563. $amount = Math::BigFloat->new($amount)->ffround( -$places ) if $places >= 0;
  564. $amount = Math::BigFloat->new($amount)->ffround( -( $places - 1 ) )
  565. if $places < 0;
  566. $amount->precision(undef); #we are assuming whole cents so do not round
  567. #immediately on arithmatic. This is necessary
  568. #because Math::BigFloat is arithmatically
  569. #correct wrt accuracy and precision.
  570. return $amount;
  571. }
  572. =item $form->db_parse_numeric('sth' => $sth, ['arrayref' => $arrayref, 'hashref' => $hashref])
  573. Converts numeric values in the result set $arrayref or $hashref to
  574. Math::BigFloat using $sth to determine which fields are numeric.
  575. =cut
  576. sub db_parse_numeric {
  577. my $self = shift;
  578. my %args = @_;
  579. my ($sth, $arrayref, $hashref) = ($args{sth}, $args{arrayref},
  580. $args{hashref});
  581. my @types = @{$sth->{TYPE}};
  582. my @names = @{$sth->{NAME_lc}};
  583. for (0 .. $#names){
  584. if ($types[$_] == 3){
  585. $arrayref[$_] = Math::BigFloat->new($arrayref[$_])
  586. if defined $arrayref;
  587. $hashref->{$names[$_]} = Math::BigFloat->new($hashref->{$names[$_]})
  588. if defined $hashref;
  589. }
  590. }
  591. return ($hashref || $arrayref);
  592. }
  593. =item Form::callproc($procname);
  594. Broken function. Use $lsmb::call_procedure instead.
  595. =cut
  596. sub callproc {
  597. my $procname = shift @_;
  598. my $argstr = "";
  599. my @results;
  600. for ( 1 .. $#_ ) {
  601. $argstr .= "?, ";
  602. }
  603. $argstr =~ s/\, $//;
  604. $query = "SELECT * FROM $procname";
  605. $query =~ s/\(\)/$argstr/;
  606. my $sth = $self->{dbh}->prepare($query);
  607. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  608. push @results, $ref;
  609. }
  610. @results;
  611. }
  612. =item $form->get_my_emp_num($myconfig);
  613. Function to get the employee number of the user $form->{login}. $myconfig is
  614. only used to create %myconfig. $form->{emp_num} is set to the retrieved value.
  615. This function is currently (2007-08-02) only used by pos.conf.pl.
  616. =cut
  617. sub get_my_emp_num {
  618. my ( $self, $myconfig) = @_;
  619. %myconfig = %{$myconfig};
  620. my $dbh = $form->{dbh};
  621. # we got a connection, check the version
  622. my $query = qq|
  623. SELECT employeenumber FROM employee
  624. WHERE login = ?|;
  625. my $sth = $dbh->prepare($query);
  626. $sth->execute( $form->{login} ) || $form->dberror($query);
  627. my ($id) = $sth->fetchrow_array;
  628. $sth->finish;
  629. $form->{'emp_num'} = $id;
  630. }
  631. =item $form->format_string(@fields);
  632. Escape the values of $form selected by @fields for the format specified by
  633. $form->{format}.
  634. =cut
  635. sub format_string {
  636. my ( $self, @fields ) = @_;
  637. my $format = $self->{format};
  638. if ( $self->{format} =~ /(postscript|pdf)/ ) {
  639. $format = 'tex';
  640. }
  641. my %replace = (
  642. 'order' => {
  643. html => [ '<', '>', '\n', '\r' ],
  644. txt => [ '\n', '\r' ],
  645. tex => [
  646. quotemeta('\\'), '&', '\n', '\r',
  647. '\$', '%', '_', '#',
  648. quotemeta('^'), '{', '}', '<',
  649. '>', '£'
  650. ]
  651. },
  652. html => {
  653. '<' => '&lt;',
  654. '>' => '&gt;',
  655. '\n' => '<br />',
  656. '\r' => '<br />'
  657. },
  658. txt => { '\n' => "\n", '\r' => "\r" },
  659. tex => {
  660. '&' => '\&',
  661. '$' => '\$',
  662. '%' => '\%',
  663. '_' => '\_',
  664. '#' => '\#',
  665. quotemeta('^') => '\^\\',
  666. '{' => '\{',
  667. '}' => '\}',
  668. '<' => '$<$',
  669. '>' => '$>$',
  670. '\n' => '\newline ',
  671. '\r' => '\newline ',
  672. '£' => '\pounds ',
  673. quotemeta('\\') => '/'
  674. }
  675. );
  676. my $key;
  677. foreach $key ( @{ $replace{order}{$format} } ) {
  678. for (@fields) { $self->{$_} =~ s/$key/$replace{$format}{$key}/g }
  679. }
  680. }
  681. =item $form->datetonum($myconfig, $date[, $picture]);
  682. Converts $date from the format $myconfig->{dateformat} to the format 'yyyymmdd'.
  683. If the year extracted is only two-digits, the year given is assumed to be in the
  684. range 2000-2099.
  685. If $date does not contain any digits, datetonum does nothing.
  686. $picture is ignored.
  687. =cut
  688. sub datetonum {
  689. my ( $self, $myconfig, $date, $picture ) = @_;
  690. if ( $date && $date =~ /\D/ ) {
  691. if ( $myconfig->{dateformat} =~ /^yy/ ) {
  692. ( $yy, $mm, $dd ) = split /\D/, $date;
  693. }
  694. elsif ( $myconfig->{dateformat} =~ /^mm/ ) {
  695. ( $mm, $dd, $yy ) = split /\D/, $date;
  696. }
  697. elsif ( $myconfig->{dateformat} =~ /^dd/ ) {
  698. ( $dd, $mm, $yy ) = split /\D/, $date;
  699. }
  700. $dd *= 1;
  701. $mm *= 1;
  702. $yy += 2000 if length $yy == 2;
  703. $dd = substr( "0$dd", -2 );
  704. $mm = substr( "0$mm", -2 );
  705. $date = "$yy$mm$dd";
  706. }
  707. $date;
  708. }
  709. =item $form->add_date($myconfig, $date, $repeat, $unit);
  710. Returns the date $repeat $units from $date in the input format. $date can
  711. either be in $myconfig->{dateformat} or 'yyyymmdd' (four digit year required for
  712. this option). The valid values for $unit are 'days', 'weeks', 'months', and
  713. 'years'.
  714. This function is unreliable for $unit values other than 'days' or 'weeks' and
  715. can die horribly.
  716. =cut
  717. sub add_date {
  718. my ( $self, $myconfig, $date, $repeat, $unit ) = @_;
  719. my $diff = 0;
  720. my $spc = $myconfig->{dateformat};
  721. my $yy;
  722. my $mm;
  723. my $dd;
  724. $spc =~ s/\w//g;
  725. $spc = substr( $spc, 0, 1 );
  726. if ($date) {
  727. if ( $date =~ /\D/ ) {
  728. if ( $myconfig->{dateformat} =~ /^yy/ ) {
  729. ( $yy, $mm, $dd ) = split /\D/, $date;
  730. }
  731. elsif ( $myconfig->{dateformat} =~ /^mm/ ) {
  732. ( $mm, $dd, $yy ) = split /\D/, $date;
  733. }
  734. elsif ( $myconfig->{dateformat} =~ /^dd/ ) {
  735. ( $dd, $mm, $yy ) = split /\D/, $date;
  736. }
  737. }
  738. else {
  739. # ISO
  740. ( $yy, $mm, $dd ) = ($date =~ /(....)(..)(..)/);
  741. }
  742. if ( $unit eq 'days' ) {
  743. $diff = $repeat * 86400;
  744. }
  745. elsif ( $unit eq 'weeks' ) {
  746. $diff = $repeat * 604800;
  747. }
  748. elsif ( $unit eq 'months' ) {
  749. $diff = $mm + $repeat;
  750. my $whole = int( $diff / 12 );
  751. $yy += $whole;
  752. $mm = ( $diff % 12 );
  753. $mm = '12' if $mm == 0;
  754. $yy-- if $mm == 12;
  755. $diff = 0;
  756. }
  757. elsif ( $unit eq 'years' ) {
  758. $yy += $repeat;
  759. }
  760. $mm--;
  761. @t = localtime( Time::Local::timelocal( 0, 0, 0, $dd, $mm, $yy ) + $diff );
  762. $t[4]++;
  763. $mm = substr( "0$t[4]", -2 );
  764. $dd = substr( "0$t[3]", -2 );
  765. $yy = $t[5] + 1900;
  766. if ( $date =~ /\D/ ) {
  767. if ( $myconfig->{dateformat} =~ /^yy/ ) {
  768. $date = "$yy$spc$mm$spc$dd";
  769. }
  770. elsif ( $myconfig->{dateformat} =~ /^mm/ ) {
  771. $date = "$mm$spc$dd$spc$yy";
  772. }
  773. elsif ( $myconfig->{dateformat} =~ /^dd/ ) {
  774. $date = "$dd$spc$mm$spc$yy";
  775. }
  776. }
  777. else {
  778. $date = "$yy$mm$dd";
  779. }
  780. }
  781. $date;
  782. }
  783. =item $form->print_button($button, $name);
  784. Outputs a submit button to STDOUT. $button is a hashref that contains data
  785. about buttons, $name is the key for the element in $button to output. Each
  786. value in $button is a reference to a hash of two elements, 'key' and 'value'.
  787. $name is the value of the button that gets sent to the server when clicked,
  788. $button->{$name}{key} is the accesskey, and $button->{$name}{value} is the label
  789. for the button.
  790. =cut
  791. sub print_button {
  792. my ( $self, $button, $name ) = @_;
  793. print
  794. qq|<button class="submit" type="submit" name="action" value="$name" accesskey="$button->{$name}{key}" title="$button->{$name}{value} [Alt-$button->{$name}{key}]">$button->{$name}{value}</button>\n|;
  795. }
  796. # Database routines used throughout
  797. =item $form->db_init($myconfig);
  798. Connect to the database that $myconfig is set to use and initialise the base
  799. parameters. The connection handle becomes $form->{dbh} and
  800. $form->{custom_db_fields} is populated. The connection initiated has
  801. autocommit disabled.
  802. =cut
  803. sub db_init {
  804. my ( $self, $myconfig ) = @_;
  805. $self->{dbh} = $self->dbconnect_noauto($myconfig) || $self->dberror();
  806. %date_query = (
  807. 'mm/dd/yy' => 'set DateStyle to \'SQL, US\'',
  808. 'mm-dd-yy' => 'set DateStyle to \'POSTGRES, US\'',
  809. 'dd/mm/yy' => 'set DateStyle to \'SQL, EUROPEAN\'',
  810. 'dd-mm-yy' => 'set DateStyle to \'POSTGRES, EUROPEAN\'',
  811. 'dd.mm.yy' => 'set DateStyle to \'GERMAN\''
  812. );
  813. $self->{dbh}->do( $date_query{ $myconfig->{dateformat} } );
  814. $self->{db_dateformat} = $myconfig->{dateformat}; #shim
  815. my $query = "SELECT t.extends,
  816. coalesce (t.table_name, 'custom_' || extends)
  817. || ':' || f.field_name as field_def
  818. FROM custom_table_catalog t
  819. JOIN custom_field_catalog f USING (table_id)";
  820. my $sth = $self->{dbh}->prepare($query);
  821. $sth->execute;
  822. my $ref;
  823. while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  824. push @{ $self->{custom_db_fields}{ $ref->{extends} } },
  825. $ref->{field_def};
  826. }
  827. }
  828. =item $form->run_custom_queries($tablename, $query_type[, $linenum]);
  829. Runs queries against custom fields for the specified $query_type against
  830. $tablename.
  831. Valid values for $query_type are any casing of 'SELECT', 'INSERT', and 'UPDATE'.
  832. =cut
  833. sub run_custom_queries {
  834. my ( $self, $tablename, $query_type, $linenum ) = @_;
  835. my $dbh = $self->{dbh};
  836. if ( $query_type !~ /^(select|insert|update)$/i ) {
  837. $self->error(
  838. $locale->text(
  839. "Passed incorrect query type to run_custom_queries."
  840. )
  841. );
  842. }
  843. my @rc;
  844. my %temphash;
  845. my @templist;
  846. my @elements;
  847. my $query;
  848. my $ins_values;
  849. if ($linenum) {
  850. $linenum = "_$linenum";
  851. }
  852. $query_type = uc($query_type);
  853. for ( @{ $self->{custom_db_fields}{$tablename} } ) {
  854. @elements = split( /:/, $_ );
  855. push @{ $temphash{ $elements[0] } }, $elements[1];
  856. }
  857. for ( keys %temphash ) {
  858. my @data;
  859. my $ins_values;
  860. $query = "$query_type ";
  861. if ( $query_type eq 'UPDATE' ) {
  862. $query = "DELETE FROM $_ WHERE row_id = ?";
  863. my $sth = $dbh->prepare($query);
  864. $sth->execute( $self->{ "id" . "$linenum" } )
  865. || $self->dberror($query);
  866. }
  867. elsif ( $query_type eq 'INSERT' ) {
  868. $query .= " INTO $_ (";
  869. }
  870. my $first = 1;
  871. for ( @{ $temphash{$_} } ) {
  872. $query .= "$_";
  873. if ( $query_type eq 'UPDATE' ) {
  874. $query .= '= ?';
  875. }
  876. $ins_values .= "?, ";
  877. $query .= ", ";
  878. $first = 0;
  879. if ( $query_type eq 'UPDATE' or $query_type eq 'INSERT' ) {
  880. push @data, $self->{"$_$linenum"};
  881. }
  882. }
  883. if ( $query_type ne 'INSERT' ) {
  884. $query =~ s/, $//;
  885. }
  886. if ( $query_type eq 'SELECT' ) {
  887. $query .= " FROM $_";
  888. }
  889. if ( $query_type eq 'SELECT' or $query_type eq 'UPDATE' ) {
  890. $query .= " WHERE row_id = ?";
  891. }
  892. if ( $query_type eq 'INSERT' ) {
  893. $query .= " row_id) VALUES ($ins_values ?)";
  894. }
  895. if ( $query_type eq 'SELECT' ) {
  896. push @rc, [$query];
  897. }
  898. else {
  899. unshift( @data, $query );
  900. push @rc, [@data];
  901. }
  902. }
  903. if ( $query_type eq 'INSERT' ) {
  904. for (@rc) {
  905. $query = shift( @{$_} );
  906. $sth = $dbh->prepare($query)
  907. || $self->db_error($query);
  908. $sth->execute( @{$_}, $self->{id} )
  909. || $self->dberror($query);
  910. $sth->finish;
  911. $did_insert = 1;
  912. }
  913. }
  914. elsif ( $query_type eq 'UPDATE' ) {
  915. @rc = $self->run_custom_queries( $tablename, 'INSERT', $linenum );
  916. }
  917. elsif ( $query_type eq 'SELECT' ) {
  918. for (@rc) {
  919. $query = shift @{$_};
  920. $sth = $self->{dbh}->prepare($query);
  921. $sth->execute( $self->{id} );
  922. $ref = $sth->fetchrow_hashref(NAME_lc);
  923. for ( keys %{$ref} ) {
  924. $self->{$_} = $ref->{$_};
  925. }
  926. }
  927. }
  928. @rc;
  929. }
  930. =item $form->dbconnect($myconfig);
  931. Returns an autocommit connection to the database specified in $myconfig.
  932. =cut
  933. sub dbconnect {
  934. my ( $self, $myconfig ) = @_;
  935. # connect to database
  936. my $dbh = DBI->connect( $myconfig->{dbconnect},
  937. $myconfig->{dbuser}, $myconfig->{dbpasswd} )
  938. or $self->dberror;
  939. $dbh->{pg_enable_utf8} = 1;
  940. # set db options
  941. if ( $myconfig->{dboptions} ) {
  942. $dbh->do( $myconfig->{dboptions} )
  943. || $self->dberror( $myconfig->{dboptions} );
  944. }
  945. $dbh;
  946. }
  947. =item $form->dbconnect_noauto($myconfig);
  948. Returns a non-autocommit connection to the database specified in $myconfig.
  949. =cut
  950. sub dbconnect_noauto {
  951. my ( $self, $myconfig ) = @_;
  952. # connect to database
  953. $dbh = DBI->connect(
  954. $myconfig->{dbconnect}, $myconfig->{dbuser},
  955. $myconfig->{dbpasswd}, { AutoCommit => 0 }
  956. ) or $self->dberror;
  957. $dbh->{pg_enable_utf8} = 1;
  958. # set db options
  959. if ( $myconfig->{dboptions} ) {
  960. $dbh->do( $myconfig->{dboptions} );
  961. }
  962. $dbh;
  963. }
  964. =item $form->dbquote($var);
  965. If $var is an empty string, return NULL, otherwise return $var as quoted by
  966. $form->{dbh}->quote($var).
  967. =cut
  968. sub dbquote {
  969. my ( $self, $var ) = @_;
  970. if ( $var eq '' ) {
  971. $_ = "NULL";
  972. }
  973. else {
  974. $_ = $self->{dbh}->quote($var);
  975. }
  976. $_;
  977. }
  978. sub update_balance {
  979. # This is a dangerous private function. All apps calling it must
  980. # be careful to avoid SQL injection issues
  981. my ( $self, $dbh, $table, $field, $where, $value ) = @_;
  982. # if we have a value, go do it
  983. if ($value) {
  984. # retrieve balance from table
  985. my $query = "SELECT $field FROM $table WHERE $where FOR UPDATE";
  986. my ($balance) = $dbh->selectrow_array($query);
  987. $balance += $value;
  988. # update balance
  989. $query = "UPDATE $table SET $field = $balance WHERE $where";
  990. $dbh->do($query) || $self->dberror($query);
  991. }
  992. }
  993. =item $form->update_exchangerate($dbh, $curr, $transdate, $buy, $sell);
  994. Updates the exchange rates $buy and $sell for the given $currency on $transdate.
  995. If there is not yet an exchange rate for $currency on $transdate, an entry is
  996. inserted. This returns without doing anything if $curr eq ''.
  997. $dbh is not used, favouring $self->{dbh}.
  998. =cut
  999. sub update_exchangerate {
  1000. my ( $self, $dbh, $curr, $transdate, $buy, $sell ) = @_;
  1001. # some sanity check for currency
  1002. return if ( $curr eq "" );
  1003. my $query = qq|
  1004. SELECT curr
  1005. FROM exchangerate
  1006. WHERE curr = ?
  1007. AND transdate = ?
  1008. FOR UPDATE|;
  1009. my $sth = $self->{dbh}->prepare($query);
  1010. $sth->execute( $curr, $transdate ) || $self->dberror($query);
  1011. my $set;
  1012. my @queryargs;
  1013. if ( $buy && $sell ) {
  1014. $set = "buy = ?, sell = ?";
  1015. @queryargs = ( $buy, $sell );
  1016. }
  1017. elsif ($buy) {
  1018. $set = "buy = ?";
  1019. @queryargs = ($buy);
  1020. }
  1021. elsif ($sell) {
  1022. $set = "sell = ?";
  1023. @queryargs = ($sell);
  1024. }
  1025. if ( !$set ) {
  1026. $self->error("Exchange rate missing!");
  1027. }
  1028. if ( $sth->fetchrow_array ) {
  1029. $query = qq|UPDATE exchangerate
  1030. SET $set
  1031. WHERE curr = ?
  1032. AND transdate = ?|;
  1033. push( @queryargs, $curr, $transdate );
  1034. }
  1035. else {
  1036. $query = qq|
  1037. INSERT INTO exchangerate (
  1038. curr, buy, sell, transdate)
  1039. VALUES (?, ?, ?, ?)|;
  1040. @queryargs = ( $curr, $buy, $sell, $transdate );
  1041. }
  1042. $sth->finish;
  1043. $sth = $self->{dbh}->prepare($query);
  1044. $sth->execute(@queryargs) || $self->dberror($query);
  1045. }
  1046. =item $form->save_exchangerate($myconfig, $currency, $transdate, $rate, $fld);
  1047. Saves the exchange rate $rate for the given $currency on $transdate for the
  1048. provided purpose in $fld. $fld can be either 'buy' or 'sell'.
  1049. $myconfig is not used. $self->update_exchangerate is used for the majority of
  1050. the work.
  1051. =cut
  1052. sub save_exchangerate {
  1053. my ( $self, $myconfig, $currency, $transdate, $rate, $fld ) = @_;
  1054. my ( $buy, $sell ) = ( 0, 0 );
  1055. $buy = $rate if $fld eq 'buy';
  1056. $sell = $rate if $fld eq 'sell';
  1057. $self->update_exchangerate( $self->{dbh}, $currency, $transdate, $buy,
  1058. $sell );
  1059. }
  1060. =item $form->get_exchangerate($dbh, $curr, $transdate, $fld);
  1061. Returns the exchange rate in relation to the default currency for $currency on
  1062. $transdate for the purpose indicated by $fld. $fld can be either 'buy' or
  1063. 'sell' to get usable results.
  1064. $dbh is not used, favouring $self->{dbh}.
  1065. =cut
  1066. sub get_exchangerate {
  1067. my ( $self, $dbh, $curr, $transdate, $fld ) = @_;
  1068. my $exchangerate = 1;
  1069. if ($transdate) {
  1070. my $query = qq|
  1071. SELECT $fld FROM exchangerate
  1072. WHERE curr = ? AND transdate = ?|;
  1073. $sth = $self->{dbh}->prepare($query);
  1074. $sth->execute( $curr, $transdate );
  1075. ($exchangerate) = $sth->fetchrow_array;
  1076. $exchangerate = Math::BigFloat->new($exchangerate);
  1077. }
  1078. $sth->finish;
  1079. $exchangerate;
  1080. }
  1081. =item $form->check_exchangerate($myconfig, $currency, $transdate, $fld);
  1082. Returns some true value when an entry for $currency on $transdate is true for
  1083. the purpose indicated by $fld. $fld can be either 'buy' or 'sell' to get
  1084. usable results. Returns false if $transdate is not set.
  1085. $myconfig is not used.
  1086. =cut
  1087. sub check_exchangerate {
  1088. my ( $self, $myconfig, $currency, $transdate, $fld ) = @_;
  1089. return "" unless $transdate;
  1090. my $query = qq|
  1091. SELECT $fld
  1092. FROM exchangerate
  1093. WHERE curr = ? AND transdate = ?|;
  1094. my $sth = $self->{dbh}->prepare($query);
  1095. $sth->execute( $currency, $transdate );
  1096. my ($exchangerate) = $sth->fetchrow_array;
  1097. $sth->finish;
  1098. $exchangerate;
  1099. }
  1100. =item $form->add_shipto($dbh, $id);
  1101. Inserts a new address into the table shipto if the value of any of the shipto
  1102. address components in $form differs to the regular attribute in $form. The
  1103. inserted value of trans_id is $id, the other fields correspond with the shipto
  1104. address components of $form.
  1105. $dbh is unused.
  1106. =cut
  1107. sub add_shipto {
  1108. my ( $self, $dbh, $id ) = @_;
  1109. my $shipto;
  1110. foreach my $item (
  1111. qw(name address1 address2 city state
  1112. zipcode country contact phone fax email)
  1113. )
  1114. {
  1115. if ( $self->{"shipto$item"} ne "" ) {
  1116. $shipto = 1 if ( $self->{$item} ne $self->{"shipto$item"} );
  1117. }
  1118. }
  1119. if ($shipto) {
  1120. my $query = qq|
  1121. INSERT INTO shipto
  1122. (trans_id, shiptoname, shiptoaddress1,
  1123. shiptoaddress2, shiptocity, shiptostate,
  1124. shiptozipcode, shiptocountry, shiptocontact,
  1125. shiptophone, shiptofax, shiptoemail)
  1126. VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1127. |;
  1128. $sth = $self->{dbh}->prepare($query) || $self->dberror($query);
  1129. $sth->execute(
  1130. $id, $self->{shiptoname},
  1131. $self->{shiptoaddress1}, $self->{shiptoaddress2},
  1132. $self->{shiptocity}, $self->{shiptostate},
  1133. $self->{shiptozipcode}, $self->{shiptocountry},
  1134. $self->{shiptocontact}, $self->{shiptophone},
  1135. $self->{shiptofax}, $self->{shiptoemail}
  1136. ) || $self->dberror($query);
  1137. $sth->finish;
  1138. }
  1139. }
  1140. =item $form->get_employee($dbh);
  1141. Returns a list containing the name and id of the employee $form->{login}. Any
  1142. portion of $form->{login} including and past '@' are ignored.
  1143. $dbh is unused.
  1144. =cut
  1145. sub get_employee {
  1146. my ( $self, $dbh ) = @_;
  1147. my $login = $self->{login};
  1148. $login =~ s/@.*//;
  1149. my $query = qq|
  1150. SELECT name, id
  1151. FROM entity WHERE id IN (select entity_id
  1152. FROM employee
  1153. WHERE login = ?)|;
  1154. $sth = $self->{dbh}->prepare($query);
  1155. $sth->execute($login);
  1156. my (@a) = $sth->fetchrow_array();
  1157. $a[1] *= 1;
  1158. $sth->finish;
  1159. @a;
  1160. }
  1161. # this sub gets the id and name from $table
  1162. sub get_name {
  1163. my ( $self, $myconfig, $table, $transdate ) = @_;
  1164. # connect to database
  1165. my @queryargs;
  1166. my $where;
  1167. if ($transdate) {
  1168. $where = qq|
  1169. AND (startdate IS NULL OR startdate <= ?)
  1170. AND (enddate IS NULL OR enddate >= ?)|;
  1171. @queryargs = ( $transdate, $transdate );
  1172. }
  1173. # Company name is stored in $self->{vendor} or $self->{customer}
  1174. my $name = $self->like( lc $self->{$table} );
  1175. # Vendor and Customer are now views into entity_credit_account.
  1176. my $query = qq|
  1177. SELECT * FROM $table t
  1178. JOIN entity e ON t.entity_id = e.id
  1179. WHERE (lower(e.name) LIKE ? OR t.${table}number LIKE ?)
  1180. $where
  1181. ORDER BY e.name|;
  1182. unshift( @queryargs, $name, $name, $table );
  1183. my $sth = $self->{dbh}->prepare($query);
  1184. $sth->execute(@queryargs) || $self->dberror($query);
  1185. my $i = 0;
  1186. @{ $self->{name_list} } = ();
  1187. while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1188. push( @{ $self->{name_list} }, $ref );
  1189. $i++;
  1190. }
  1191. $sth->finish;
  1192. return $i;
  1193. }
  1194. sub all_vc {
  1195. my ( $self, $myconfig, $vc, $module, $dbh, $transdate, $job ) = @_;
  1196. my $ref;
  1197. my $disconnect = 0;
  1198. $dbh = $self->{dbh};
  1199. my $sth;
  1200. if ($vc eq 'customer'){
  1201. $self->{vc_class} = 2;
  1202. } else {
  1203. $self->{vc_class} = 1;
  1204. }
  1205. my $query = qq|SELECT count(*) FROM entity_credit_account where entity_class = ?|;
  1206. my $where;
  1207. my @queryargs = ($self->{vc_class});
  1208. if ($transdate) {
  1209. $query .= qq| AND (startdate IS NULL OR startdate <= ?)
  1210. AND (enddate IS NULL OR enddate >= ?)|;
  1211. push (@queryargs, $transdate, $transdate );
  1212. }
  1213. $sth = $dbh->prepare($query);
  1214. $sth->execute(@queryargs);
  1215. my ($count) = $sth->fetchrow_array;
  1216. $sth->finish;
  1217. @queryargs = ();
  1218. # build selection list
  1219. if ( $count < $myconfig->{vclimit} ) {
  1220. $self->{"${vc}_id"} *= 1;
  1221. $where = "AND $where" if $where;
  1222. $query = qq|SELECT id, name
  1223. FROM entity
  1224. WHERE id IN (select entity_id
  1225. FROM $vc)
  1226. $where
  1227. UNION
  1228. SELECT id,name
  1229. FROM entity
  1230. WHERE id = ?
  1231. ORDER BY name|;
  1232. push( @queryargs, $self->{"${vc}_id"} );
  1233. $sth = $dbh->prepare($query);
  1234. $sth->execute(@queryargs) || $self->dberror($query);
  1235. @{ $self->{"all_$vc"} } = ();
  1236. while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1237. push @{ $self->{"all_$vc"} }, $ref;
  1238. }
  1239. $sth->finish;
  1240. }
  1241. # get self
  1242. if ( !$self->{employee_id} ) {
  1243. ( $self->{employee}, $self->{employee_id} ) = split /--/,
  1244. $self->{employee};
  1245. ( $self->{employee}, $self->{employee_id} ) = $self->get_employee($dbh)
  1246. unless $self->{employee_id};
  1247. }
  1248. $self->all_employees( $myconfig, $dbh, $transdate, 1 );
  1249. $self->all_departments( $myconfig, $dbh, $vc );
  1250. $self->all_projects( $myconfig, $dbh, $transdate, $job );
  1251. # get language codes
  1252. $query = qq|SELECT *
  1253. FROM language
  1254. ORDER BY 2|;
  1255. $sth = $dbh->prepare($query);
  1256. $sth->execute || $self->dberror($query);
  1257. $self->{all_language} = ();
  1258. while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1259. push @{ $self->{all_language} }, $ref;
  1260. }
  1261. $sth->finish;
  1262. $self->all_taxaccounts( $myconfig, $dbh, $transdate );
  1263. }
  1264. sub all_taxaccounts {
  1265. my ( $self, $myconfig, $dbh2, $transdate ) = @_;
  1266. my $dbh = $self->{dbh};
  1267. my $sth;
  1268. my $query;
  1269. my $where;
  1270. my @queryargs = ();
  1271. if ($transdate) {
  1272. $where = qq| AND (t.validto >= ? OR t.validto IS NULL)|;
  1273. push( @queryargs, $transdate );
  1274. }
  1275. if ( $self->{taxaccounts} ) {
  1276. # rebuild tax rates
  1277. $query = qq|SELECT t.rate, t.taxnumber
  1278. FROM tax t
  1279. JOIN chart c ON (c.id = t.chart_id)
  1280. WHERE c.accno = ?
  1281. $where
  1282. ORDER BY accno, validto|;
  1283. $sth = $dbh->prepare($query) || $self->dberror($query);
  1284. foreach my $accno ( split / /, $self->{taxaccounts} ) {
  1285. $sth->execute( $accno, @queryargs );
  1286. ( $self->{"${accno}_rate"}, $self->{"${accno}_taxnumber"} ) =
  1287. $sth->fetchrow_array;
  1288. $sth->finish;
  1289. }
  1290. }
  1291. }
  1292. =item $form->all_employees($myconfig, $dbh2, $transdate, $sales);
  1293. Sets $form->{all_employee} to be a reference to an array referencing hashes of
  1294. employee information. The hashes are of the form {'id' => id, 'name' => name}.
  1295. If $transdate is set, the query is limited to employees who are active on that
  1296. day. If $sales is true, only employees with the sales flag set are added.
  1297. $dbh2 is unused.
  1298. =cut
  1299. sub all_employees {
  1300. my ( $self, $myconfig, $dbh2, $transdate, $sales ) = @_;
  1301. my $dbh = $self->{dbh};
  1302. my @whereargs = ();
  1303. # setup employees/sales contacts
  1304. my $query = qq|
  1305. SELECT id, name
  1306. FROM entity
  1307. WHERE id IN (SELECT entity_id FROM employee
  1308. WHERE|;
  1309. if ($transdate) {
  1310. $query .= qq| (startdate IS NULL OR startdate <= ?)
  1311. AND (enddate IS NULL OR enddate >= ?) AND|;
  1312. @whereargs = ( $transdate, $transdate );
  1313. }
  1314. else {
  1315. $query .= qq| enddate IS NULL AND|;
  1316. }
  1317. if ($sales) {
  1318. $query .= qq| sales = '1' AND|;
  1319. }
  1320. $query =~ s/(WHERE|AND)$//;
  1321. $query .= qq|) ORDER BY name|;
  1322. my $sth = $dbh->prepare($query);
  1323. $sth->execute(@whereargs) || $self->dberror($query);
  1324. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1325. push @{ $self->{all_employee} }, $ref;
  1326. }
  1327. $sth->finish;
  1328. }
  1329. sub all_projects {
  1330. my ( $self, $myconfig, $dbh2, $transdate, $job ) = @_;
  1331. my $dbh = $self->{dbh};
  1332. my @queryargs = ();
  1333. my $where = "1 = 1";
  1334. $where = qq|id NOT IN (SELECT id
  1335. FROM parts
  1336. WHERE project_id > 0)| if !$job;
  1337. my $query = qq|SELECT *
  1338. FROM project
  1339. WHERE $where|;
  1340. if ( $self->{language_code} ) {
  1341. $query = qq|
  1342. SELECT pr.*, t.description AS translation
  1343. FROM project pr
  1344. LEFT JOIN translation t ON (t.trans_id = pr.id)
  1345. WHERE t.language_code = ?|;
  1346. push( @queryargs, $self->{language_code} );
  1347. }
  1348. if ($transdate) {
  1349. $query .= qq| AND (startdate IS NULL OR startdate <= ?)
  1350. AND (enddate IS NULL OR enddate >= ?)|;
  1351. push( @queryargs, $transdate, $transdate );
  1352. }
  1353. $query .= qq| ORDER BY projectnumber|;
  1354. $sth = $dbh->prepare($query);
  1355. $sth->execute(@queryargs) || $self->dberror($query);
  1356. @{ $self->{all_project} } = ();
  1357. while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1358. push @{ $self->{all_project} }, $ref;
  1359. }
  1360. $sth->finish;
  1361. }
  1362. =item $form->all_departments($myconfig, $dbh2, $vc);
  1363. Set $form->{all_department} to be a reference to a list of hashrefs describing
  1364. departments of the form {'id' => id, 'description' => description}. If $vc
  1365. is 'customer', further limit the results to those whose role is 'P' (Profit
  1366. Center).
  1367. This procedure is internally followed by a call to $form->all_years($myconfig).
  1368. $dbh2 is not used.
  1369. =cut
  1370. sub all_departments {
  1371. my ( $self, $myconfig, $dbh2, $vc ) = @_;
  1372. $dbh = $self->{dbh};
  1373. my $where = "1 = 1";
  1374. if ($vc) {
  1375. if ( $vc eq 'customer' ) {
  1376. $where = " role = 'P'";
  1377. }
  1378. }
  1379. my $query = qq|SELECT id, description
  1380. FROM department
  1381. WHERE $where
  1382. ORDER BY 2|;
  1383. my $sth = $dbh->prepare($query);
  1384. $sth->execute || $self->dberror($query);
  1385. @{ $self->{all_department} } = ();
  1386. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1387. push @{ $self->{all_department} }, $ref;
  1388. }
  1389. $sth->finish;
  1390. $self->all_years($myconfig);
  1391. }
  1392. =item $form->all_years($myconfig[, $dbh2]);
  1393. Populates the hash $form->{all_month} with a mapping between a two-digit month
  1394. number and the English month name. Populates the list $form->{all_years} with
  1395. the years between the year of the oldest transaction date in acc_trans and the
  1396. newest, inclusive.
  1397. $dbh2 is unused.
  1398. =cut
  1399. sub all_years {
  1400. my ( $self, $myconfig, $dbh2 ) = @_;
  1401. $dbh = $self->{dbh};
  1402. # get years
  1403. my $query = qq|
  1404. SELECT (SELECT transdate FROM acc_trans ORDER BY transdate asc
  1405. LIMIT 1),
  1406. (SELECT transdate FROM acc_trans ORDER BY transdate desc
  1407. LIMIT 1)|;
  1408. my ( $startdate, $enddate ) = $dbh->selectrow_array($query);
  1409. if ( $myconfig->{dateformat} =~ /^yy/ ) {
  1410. ($startdate) = split /\W/, $startdate;
  1411. ($enddate) = split /\W/, $enddate;
  1412. }
  1413. else {
  1414. (@_) = split /\W/, $startdate;
  1415. $startdate = $_[2];
  1416. (@_) = split /\W/, $enddate;
  1417. $enddate = $_[2];
  1418. }
  1419. $self->{all_years} = ();
  1420. $startdate = substr( $startdate, 0, 4 );
  1421. $enddate = substr( $enddate, 0, 4 );
  1422. while ( $enddate >= $startdate ) {
  1423. push @{ $self->{all_years} }, $enddate--;
  1424. }
  1425. #this should probably be changed to use locale
  1426. %{ $self->{all_month} } = (
  1427. '01' => 'January',
  1428. '02' => 'February',
  1429. '03' => 'March',
  1430. '04' => 'April',
  1431. '05' => 'May ',
  1432. '06' => 'June',
  1433. '07' => 'July',
  1434. '08' => 'August',
  1435. '09' => 'September',
  1436. '10' => 'October',
  1437. '11' => 'November',
  1438. '12' => 'December'
  1439. );
  1440. }
  1441. sub create_links {
  1442. my ( $self, $module, $myconfig, $vc, $job ) = @_;
  1443. # get last customers or vendors
  1444. my ( $query, $sth );
  1445. if (!$self->{dbh}) {
  1446. $self->db_init($myconfig);
  1447. }
  1448. $dbh = $self->{dbh};
  1449. my %xkeyref = ();
  1450. # now get the account numbers
  1451. $query = qq|SELECT accno, description, link
  1452. FROM chart
  1453. WHERE link LIKE ?
  1454. ORDER BY accno|;
  1455. $sth = $dbh->prepare($query);
  1456. $sth->execute( "%" . "$module%" ) || $self->dberror($query);
  1457. $self->{accounts} = "";
  1458. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1459. foreach my $key ( split /:/, $ref->{link} ) {
  1460. if ( $key =~ /$module/ ) {
  1461. # cross reference for keys
  1462. $xkeyref{ $ref->{accno} } = $key;
  1463. push @{ $self->{"${module}_links"}{$key} },
  1464. {
  1465. accno => $ref->{accno},
  1466. description => $ref->{description}
  1467. };
  1468. $self->{accounts} .= "$ref->{accno} "
  1469. unless $key =~ /tax/;
  1470. }
  1471. }
  1472. }
  1473. $sth->finish;
  1474. my $arap = ( $vc eq 'customer' ) ? 'ar' : 'ap';
  1475. if ( $self->{id} ) {
  1476. $query = qq|
  1477. SELECT a.invnumber, a.transdate,
  1478. a.${vc}_id, a.datepaid, a.duedate, a.ordnumber,
  1479. a.taxincluded, a.curr AS currency, a.notes,
  1480. a.intnotes, c.name AS $vc, a.department_id,
  1481. d.description AS department,
  1482. a.amount AS oldinvtotal, a.paid AS oldtotalpaid,
  1483. a.employee_id, e.name AS employee,
  1484. c.language_code, a.ponumber, a.reverse
  1485. FROM $arap a
  1486. JOIN $vc c ON (a.${vc}_id = c.id)
  1487. LEFT JOIN employee e ON (e.id = a.employee_id)
  1488. LEFT JOIN department d ON (d.id = a.department_id)
  1489. WHERE a.id = ?|;
  1490. $sth = $dbh->prepare($query);
  1491. $sth->execute( $self->{id} ) || $self->dberror($query);
  1492. $ref = $sth->fetchrow_hashref(NAME_lc);
  1493. $self->db_parse_numeric(sth=>$sth, hashref=>$ref);
  1494. foreach $key ( keys %$ref ) {
  1495. $self->{$key} = $ref->{$key};
  1496. }
  1497. $sth->finish;
  1498. # get printed, emailed
  1499. $query = qq|
  1500. SELECT s.printed, s.emailed, s.spoolfile, s.formname
  1501. FROM status s WHERE s.trans_id = ?|;
  1502. $sth = $dbh->prepare($query);
  1503. $sth->execute( $self->{id} ) || $self->dberror($query);
  1504. while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1505. $self->{printed} .= "$ref->{formname} "
  1506. if $ref->{printed};
  1507. $self->{emailed} .= "$ref->{formname} "
  1508. if $ref->{emailed};
  1509. $self->{queued} .= "$ref->{formname} " . "$ref->{spoolfile} "
  1510. if $ref->{spoolfile};
  1511. }
  1512. $sth->finish;
  1513. for (qw(printed emailed queued)) { $self->{$_} =~ s/ +$//g }
  1514. # get recurring
  1515. $self->get_recurring($dbh);
  1516. # get amounts from individual entries
  1517. $query = qq|
  1518. SELECT c.accno, c.description, a.source, a.amount,
  1519. a.memo, a.transdate, a.cleared, a.project_id,
  1520. p.projectnumber
  1521. FROM acc_trans a
  1522. JOIN chart c ON (c.id = a.chart_id)
  1523. LEFT JOIN project p ON (p.id = a.project_id)
  1524. WHERE a.trans_id = ?
  1525. AND a.fx_transaction = '0'
  1526. ORDER BY transdate|;
  1527. $sth = $dbh->prepare($query);
  1528. $sth->execute( $self->{id} ) || $self->dberror($query);
  1529. my $fld = ( $vc eq 'customer' ) ? 'buy' : 'sell';
  1530. $self->{exchangerate} =
  1531. $self->get_exchangerate( $dbh, $self->{currency}, $self->{transdate},
  1532. $fld );
  1533. # store amounts in {acc_trans}{$key} for multiple accounts
  1534. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1535. $ref->{exchangerate} =
  1536. $self->get_exchangerate( $dbh, $self->{currency},
  1537. $ref->{transdate}, $fld );
  1538. if ($form->{reverse}){
  1539. $ref->{amount} *= -1;
  1540. }
  1541. push @{ $self->{acc_trans}{ $xkeyref{ $ref->{accno} } } }, $ref;
  1542. }
  1543. $sth->finish;
  1544. }
  1545. else {
  1546. if ( !$self->{"$self->{vc}_id"} ) {
  1547. $self->lastname_used( $myconfig, $dbh, $vc, $module );
  1548. }
  1549. }
  1550. for (qw(current_date curr closedto revtrans)) {
  1551. if ($_ eq 'closedto'){
  1552. $query = qq|
  1553. SELECT value::date FROM defaults
  1554. WHERE setting_key = '$_'|;
  1555. } elsif ($_ eq 'current_date') {
  1556. $query = qq| select $_|;
  1557. } else {
  1558. $query = qq|
  1559. SELECT value FROM defaults
  1560. WHERE setting_key = '$_'|;
  1561. }
  1562. $sth = $dbh->prepare($query);
  1563. $sth->execute || $self->dberror($query);
  1564. ($val) = $sth->fetchrow_array();
  1565. if ( $_ eq 'curr' ) {
  1566. $self->{currencies} = $val;
  1567. }
  1568. else {
  1569. $self->{$_} = $val;
  1570. }
  1571. $sth->finish;
  1572. }
  1573. if (!$self->{id}){
  1574. $self->{transdate} = $self->{current_date};
  1575. }
  1576. $self->all_vc( $myconfig, $vc, $module, $dbh, $self->{transdate}, $job );
  1577. }
  1578. sub lastname_used {
  1579. my ( $self, $myconfig, $dbh2, $vc, $module ) = @_;
  1580. my $dbh = $self->{dbh};
  1581. $vc ||= $self->{vc}; # add default to correct for improper passing
  1582. my $arap = ( $vc eq 'customer' ) ? "ar" : "ap";
  1583. my $sth;
  1584. if ( $self->{type} =~ /_order/ ) {
  1585. $arap = 'oe';
  1586. $where = "quotation = '0'";
  1587. }
  1588. if ( $self->{type} =~ /_quotation/ ) {
  1589. $arap = 'oe';
  1590. $where = "quotation = '1'";
  1591. }
  1592. $where = "AND $where " if $where;
  1593. $inv_notes = "ct.invoice_notes," if $vc eq 'customer';
  1594. my $query = qq|
  1595. SELECT entity.name, ct.curr AS currency, entity_id AS ${vc}_id,
  1596. current_date + ct.terms AS duedate,
  1597. $inv_notes
  1598. ct.curr AS currency
  1599. FROM $vc ct
  1600. JOIN entity ON (ct.entity_id = entity.id)
  1601. WHERE entity.id = (select entity_id from $arap
  1602. where entity_id IS NOT NULL $where
  1603. order by id DESC limit 1)|;
  1604. $sth = $self->{dbh}->prepare($query);
  1605. $sth->execute() || $self->dberror($query);
  1606. my $ref = $sth->fetchrow_hashref(NAME_lc);
  1607. for ( keys %$ref ) { $self->{$_} = $ref->{$_} }
  1608. $sth->finish;
  1609. }
  1610. =item $form->current_date($myconfig[, $thisdate, $days]);
  1611. If $thisdate is false, get the current date from the database.
  1612. If $thisdate is true, get the date $days days from $thisdate in the date
  1613. format specified by $myconfig->{dateformat} from the database.
  1614. =cut
  1615. sub current_date {
  1616. my ( $self, $myconfig, $thisdate, $days ) = @_;
  1617. my $dbh = $self->{dbh};
  1618. my $query;
  1619. $days *= 1;
  1620. if ($thisdate) {
  1621. my $dateformat = $myconfig->{dateformat};
  1622. if ( $myconfig->{dateformat} !~ /^y/ ) {
  1623. my @a = split /\D/, $thisdate;
  1624. $dateformat .= "yy" if ( length $a[2] > 2 );
  1625. }
  1626. if ( $thisdate !~ /\D/ ) {
  1627. $dateformat = 'yyyymmdd';
  1628. }
  1629. $query = qq|SELECT (to_date(?, ?)
  1630. + ?::interval)::date AS thisdate|;
  1631. @queryargs = ( $thisdate, $dateformat, sprintf('%d days', $days) );
  1632. }
  1633. else {
  1634. $query = qq|SELECT current_date AS thisdate|;
  1635. @queryargs = ();
  1636. }
  1637. $sth = $dbh->prepare($query);
  1638. $sth->execute(@queryargs);
  1639. ($thisdate) = $sth->fetchrow_array;
  1640. $thisdate;
  1641. }
  1642. =item $form->like($str);
  1643. Returns '%$str%'
  1644. =cut
  1645. sub like {
  1646. my ( $self, $str ) = @_;
  1647. "%$str%";
  1648. }
  1649. sub redo_rows {
  1650. my ( $self, $flds, $new, $count, $numrows ) = @_;
  1651. my @ndx = ();
  1652. for ( 1 .. $count ) {
  1653. push @ndx, { num => $new->[ $_ - 1 ]->{runningnumber}, ndx => $_ };
  1654. }
  1655. my $i = 0;
  1656. # fill rows
  1657. foreach my $item ( sort { $a->{num} <=> $b->{num} } @ndx ) {
  1658. $i++;
  1659. $j = $item->{ndx} - 1;
  1660. for ( @{$flds} ) { $self->{"${_}_$i"} = $new->[$j]->{$_} }
  1661. }
  1662. # delete empty rows
  1663. for $i ( $count + 1 .. $numrows ) {
  1664. for ( @{$flds} ) { delete $self->{"${_}_$i"} }
  1665. }
  1666. }
  1667. sub get_partsgroup {
  1668. my ( $self, $myconfig, $p ) = @_;
  1669. my $dbh = $self->{dbh};
  1670. my $query = qq|SELECT DISTINCT pg.id, pg.partsgroup
  1671. FROM partsgroup pg
  1672. JOIN parts p ON (p.partsgroup_id = pg.id)|;
  1673. my $where;
  1674. my $sortorder = "partsgroup";
  1675. if ( $p->{searchitems} eq 'part' ) {
  1676. $where = qq| WHERE (p.inventory_accno_id > 0
  1677. AND p.income_accno_id > 0)|;
  1678. }
  1679. if ( $p->{searchitems} eq 'service' ) {
  1680. $where = qq| WHERE p.inventory_accno_id IS NULL|;
  1681. }
  1682. if ( $p->{searchitems} eq 'assembly' ) {
  1683. $where = qq| WHERE p.assembly = '1'|;
  1684. }
  1685. if ( $p->{searchitems} eq 'labor' ) {
  1686. $where =
  1687. qq| WHERE p.inventory_accno_id > 0 AND p.income_accno_id IS NULL|;
  1688. }
  1689. if ( $p->{searchitems} eq 'nolabor' ) {
  1690. $where = qq| WHERE p.income_accno_id > 0|;
  1691. }
  1692. if ( $p->{all} ) {
  1693. $query = qq|SELECT id, partsgroup
  1694. FROM partsgroup|;
  1695. }
  1696. my @queryargs = ();
  1697. if ( $p->{language_code} ) {
  1698. $sortorder = "translation";
  1699. $query = qq|
  1700. SELECT DISTINCT pg.id, pg.partsgroup,
  1701. t.description AS translation
  1702. FROM partsgroup pg
  1703. JOIN parts p ON (p.partsgroup_id = pg.id)
  1704. LEFT JOIN translation t ON (t.trans_id = pg.id
  1705. AND t.language_code = ?)|;
  1706. @queryargs = ( $p->{language_code} );
  1707. }
  1708. $query .= qq| $where ORDER BY $sortorder|;
  1709. my $sth = $dbh->prepare($query);
  1710. $sth->execute(@queryargs) || $self->dberror($query);
  1711. $self->{all_partsgroup} = ();
  1712. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1713. push @{ $self->{all_partsgroup} }, $ref;
  1714. }
  1715. $sth->finish;
  1716. }
  1717. =item $form->update_status($myconfig);
  1718. DELETEs all status rows which have a formname of $form->{formname} and a
  1719. trans_id of $form->{id}. INSERTs a new row into status where trans_id is
  1720. $form->{id}, formname is $form->{formname}, printed and emailed are true if
  1721. their respective $form attributes match /$form->{formname}/,,and spoolfile is
  1722. the file extracted from the string $form->{queued} or NULL if there is no entry
  1723. for $form->{formname}.
  1724. =cut
  1725. sub update_status {
  1726. my ( $self, $myconfig ) = @_;
  1727. # no id return
  1728. return unless $self->{id};
  1729. my $dbh = $self->{dbh};
  1730. my %queued = split / +/, $self->{queued};
  1731. my $spoolfile =
  1732. ( $queued{ $self->{formname} } )
  1733. ? "'$queued{$self->{formname}}'"
  1734. : 'NULL';
  1735. my $query = qq|DELETE FROM status
  1736. WHERE formname = ?
  1737. AND trans_id = ?|;
  1738. $sth = $dbh->prepare($query);
  1739. $sth->execute( $self->{formname}, $self->{id} ) || $self->dberror($query);
  1740. $sth->finish;
  1741. my $printed = ( $self->{printed} =~ /$self->{formname}/ ) ? "1" : "0";
  1742. my $emailed = ( $self->{emailed} =~ /$self->{formname}/ ) ? "1" : "0";
  1743. $query = qq|
  1744. INSERT INTO status
  1745. (trans_id, printed, emailed, spoolfile, formname)
  1746. VALUES (?, ?, ?, ?, ?)|;
  1747. $sth = $dbh->prepare($query);
  1748. $sth->execute( $self->{id}, $printed, $emailed, $spoolfile,
  1749. $self->{formname} );
  1750. $sth->finish;
  1751. }
  1752. sub save_status {
  1753. my ($self) = @_;
  1754. $dbh = $self->{dbh};
  1755. my $formnames = $self->{printed};
  1756. my $emailforms = $self->{emailed};
  1757. my $query = qq|DELETE FROM status
  1758. WHERE trans_id = ?|;
  1759. my $sth = $dbh->prepare($query);
  1760. $sth->execute( $self->{id} );
  1761. $sth->finish;
  1762. my %queued;
  1763. my $formname;
  1764. if ( $self->{queued} ) {
  1765. %queued = split / +/, $self->{queued};
  1766. foreach $formname ( keys %queued ) {
  1767. $printed = ( $self->{printed} =~ /$formname/ ) ? "1" : "0";
  1768. $emailed = ( $self->{emailed} =~ /$formname/ ) ? "1" : "0";
  1769. if ( $queued{$formname} ) {
  1770. $query = qq|
  1771. INSERT INTO status
  1772. (trans_id, printed, emailed,
  1773. spoolfile, formname)
  1774. VALUES (?, ?, ?, ?, ?)|;
  1775. $sth = $dbh->prepare($query);
  1776. $sth->execute( $self->{id}, $pinted, $emailed,
  1777. $queued{$formname}, $formname )
  1778. || $self->dberror($query);
  1779. $sth->finish;
  1780. }
  1781. $formnames =~ s/$formname//;
  1782. $emailforms =~ s/$formname//;
  1783. }
  1784. }
  1785. # save printed, emailed info
  1786. $formnames =~ s/^ +//g;
  1787. $emailforms =~ s/^ +//g;
  1788. my %status = ();
  1789. for ( split / +/, $formnames ) { $status{$_}{printed} = 1 }
  1790. for ( split / +/, $emailforms ) { $status{$_}{emailed} = 1 }
  1791. foreach my $formname ( keys %status ) {
  1792. $printed = ( $formnames =~ /$self->{formname}/ ) ? "1" : "0";
  1793. $emailed = ( $emailforms =~ /$self->{formname}/ ) ? "1" : "0";
  1794. $query = qq|
  1795. INSERT INTO status (trans_id, printed, emailed,
  1796. formname)
  1797. VALUES (?, ?, ?, ?)|;
  1798. $sth = $dbh->prepare($query);
  1799. $sth->execute( $self->{id}, $printed, $emailed, $formname );
  1800. $sth->finish;
  1801. }
  1802. $dbh->commit;
  1803. }
  1804. sub get_recurring {
  1805. my ($self) = @_;
  1806. $dbh = $self->{dbh};
  1807. my $query = qq/
  1808. SELECT s.*, se.formname || ':' || se.format AS emaila,
  1809. se.message, sp.formname || ':' ||
  1810. sp.format || ':' || sp.printer AS printa
  1811. FROM recurring s
  1812. LEFT JOIN recurringemail se ON (s.id = se.id)
  1813. LEFT JOIN recurringprint sp ON (s.id = sp.id)
  1814. WHERE s.id = ?/;
  1815. my $sth = $dbh->prepare($query);
  1816. $sth->execute( $self->{id} ) || $self->dberror($query);
  1817. for (qw(email print)) { $self->{"recurring$_"} = "" }
  1818. while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {
  1819. for ( keys %$ref ) { $self->{"recurring$_"} = $ref->{$_} }
  1820. $self->{recurringemail} .= "$ref->{emaila}:";
  1821. $self->{recurringprint} .= "$ref->{printa}:";
  1822. for (qw(emaila printa)) { delete $self->{"recurring$_"} }
  1823. }
  1824. $sth->finish;
  1825. chop $self->{recurringemail};
  1826. chop $self->{recurringprint};
  1827. if ( $self->{recurringstartdate} ) {
  1828. $self->{recurringreference} =
  1829. $self->escape( $self->{recurringreference}, 1 );
  1830. $self->{recurringmessage} =
  1831. $self->escape( $self->{recurringmessage}, 1 );
  1832. for (
  1833. qw(reference startdate repeat unit howmany
  1834. payment print email message)
  1835. )
  1836. {
  1837. $self->{recurring} .= qq|$self->{"recurring$_"},|;
  1838. }
  1839. chop $self->{recurring};
  1840. }
  1841. }
  1842. =item $form->save_recurring($dbh2, $myconfig);
  1843. Saves or deletes recurring transaction scheduling. $form->{id} is used to
  1844. determine the id used in the various recurring tables. A recurring transaction
  1845. schedule is deleted by having $form->{recurring} be false. For adding or
  1846. updating a schedule, $form->{recurring} is a comma seperated field with partial
  1847. subfield quoting of the form:
  1848. reference,startdate,repeat,unit,howmany,payment,print,email,message
  1849. text date int text int int text text text
  1850. =over
  1851. =item reference
  1852. A URI-encoded reference string for the recurrence set.
  1853. =item startdate
  1854. The index date for the recurrence.
  1855. =item repeat
  1856. The unitless repetition frequency.
  1857. =item unit
  1858. The interval unit used. Can be 'days', 'weeks', 'months', or 'years',
  1859. capitalisation and pluralisation ignored.
  1860. =item howmany
  1861. The number of recurrences for the transaction.
  1862. =item payment
  1863. Flag to indicate if a payment is included in the transaction.
  1864. =item print
  1865. A colon seperated list of formname:format:printer triplets.
  1866. =item email
  1867. A colon seperated list of formname:format pairs.
  1868. =item message
  1869. A URI-encoded message for the emails to be sent.
  1870. =back
  1871. Values for the nextdate and enddate columns of the recurring table are
  1872. calculated using startdate, repeat, unit, howmany, and the current database
  1873. date. All other fields of the recurring, recurringemail, and recurringprint are
  1874. obtained directly from $form->{recurring}.
  1875. B<WARNING>: This function does not check the validity of most subfields of
  1876. $form->{recurring}.
  1877. $dbh2 is not used.
  1878. =cut
  1879. sub save_recurring {
  1880. my ( $self, $dbh2, $myconfig ) = @_;
  1881. my $dbh = $self->{dbh};
  1882. my $query;
  1883. $query = qq|DELETE FROM recurring
  1884. WHERE id = ?|;
  1885. $sth = $dbh->prepare($query) || $self->dberror($query);
  1886. $sth->execute( $self->{id} ) || $self->dberror($query);
  1887. $query = qq|DELETE FROM recurringemail
  1888. WHERE id = ?|;
  1889. $sth = $dbh->prepare($query) || $self->dberror($query);
  1890. $sth->execute( $self->{id} ) || $self->dberror($query);
  1891. $query = qq|DELETE FROM recurringprint
  1892. WHERE id = ?|;
  1893. $sth = $dbh->prepare($query) || $self->dberror($query);
  1894. $sth->execute( $self->{id} ) || $self->dberror($query);
  1895. if ( $self->{recurring} ) {
  1896. my %s = ();
  1897. (
  1898. $s{reference}, $s{startdate}, $s{repeat},
  1899. $s{unit}, $s{howmany}, $s{payment},
  1900. $s{print}, $s{email}, $s{message}
  1901. ) = split /,/, $self->{recurring};
  1902. if ($s{unit} !~ /^(day|week|month|year)s?$/i){
  1903. $dbh->rollback;
  1904. $self->error("Invalid recurrence unit");
  1905. }
  1906. if ($s{howmany} == 0){
  1907. $self->error("Cannot set to recur 0 times");
  1908. }
  1909. for (qw(reference message)) { $s{$_} = $self->unescape( $s{$_} ) }
  1910. for (qw(repeat howmany payment)) { $s{$_} *= 1 }
  1911. # calculate enddate
  1912. my $advance = $s{repeat} * ( $s{howmany} - 1 );
  1913. my %interval;
  1914. $interval{'Pg'} =
  1915. "(?::date + interval '$advance $s{unit}')";
  1916. $query = qq|SELECT $interval{$myconfig->{dbdriver}}|;
  1917. my ($enddate) = $dbh->selectrow_array($query, undef, $s{startdate});
  1918. # calculate nextdate
  1919. $query = qq|
  1920. SELECT current_date - ?::date AS a,
  1921. ?::date - current_date AS b|;
  1922. $sth = $dbh->prepare($query) || $self->dberror($query);
  1923. $sth->execute( $s{startdate}, $enddate );
  1924. my ( $a, $b ) = $sth->fetchrow_array;
  1925. if ( $a + $b ) {
  1926. $advance =
  1927. int( ( $a / ( $a + $b ) ) * ( $s{howmany} - 1 ) + 1 ) *
  1928. $s{repeat};
  1929. }
  1930. else {
  1931. $advance = 0;
  1932. }
  1933. my $nextdate = $enddate;
  1934. if ( $advance > 0 ) {
  1935. if ( $advance < ( $s{repeat} * $s{howmany} ) ) {
  1936. $query = qq|SELECT (?::date + interval '$advance $s{unit}')|;
  1937. ($nextdate) = $dbh->selectrow_array($query, undef, $s{startdate});
  1938. }
  1939. }
  1940. else {
  1941. $nextdate = $s{startdate};
  1942. }
  1943. if ( $self->{recurringnextdate} ) {
  1944. $nextdate = $self->{recurringnextdate};
  1945. $query = qq|SELECT ?::date - ?::date|;
  1946. if ( $dbh->selectrow_array($query, undef, $enddate, $nextdate) < 0 ) {
  1947. undef $nextdate;
  1948. }
  1949. }
  1950. $self->{recurringpayment} *= 1;
  1951. $query = qq|
  1952. INSERT INTO recurring
  1953. (id, reference, startdate, enddate, nextdate,
  1954. repeat, unit, howmany, payment)
  1955. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)|;
  1956. $sth = $dbh->prepare($query);
  1957. $sth->execute(
  1958. $self->{id}, $s{reference}, $s{startdate},
  1959. $enddate, $nextdate, $s{repeat},
  1960. $s{unit}, $s{howmany}, $s{payment}
  1961. );
  1962. my @p;
  1963. my $p;
  1964. my $i;
  1965. my $sth;
  1966. if ( $s{email} ) {
  1967. # formname:format
  1968. @p = split /:/, $s{email};
  1969. $query =
  1970. qq|INSERT INTO recurringemail (id, formname, format, message)
  1971. VALUES (?, ?, ?, ?)|;
  1972. $sth = $dbh->prepare($query) || $self->dberror($query);
  1973. for ( $i = 0 ; $i <= $#p ; $i += 2 ) {
  1974. $sth->execute( $self->{id}, $p[$i], $p[ $i + 1 ], $s{message} );
  1975. }
  1976. $sth->finish;
  1977. }
  1978. if ( $s{print} ) {
  1979. # formname:format:printer
  1980. @p = split /:/, $s{print};
  1981. $query =
  1982. qq|INSERT INTO recurringprint (id, formname, format, printer)
  1983. VALUES (?, ?, ?, ?)|;
  1984. $sth = $dbh->prepare($query) || $self->dberror($query);
  1985. for ( $i = 0 ; $i <= $#p ; $i += 3 ) {
  1986. $p = ( $p[ $i + 2 ] ) ? $p[ $i + 2 ] : "";
  1987. $sth->execute( $self->{id}, $p[$i], $p[ $i + 1 ], $p );
  1988. }
  1989. $sth->finish;
  1990. }
  1991. }
  1992. $dbh->commit;
  1993. }
  1994. =item $form->save_intnotes($myconfig, $vc);
  1995. Sets the intnotes field of the entry in the table $vc that has the id
  1996. $form->{id} to the value of $form->{intnotes}.
  1997. Does nothing if $form->{id} is not set.
  1998. =cut
  1999. sub save_intnotes {
  2000. my ( $self, $myconfig, $vc ) = @_;
  2001. # no id return
  2002. return unless $self->{id};
  2003. my $dbh = $self->{dbh};
  2004. my $query = qq|UPDATE $vc SET intnotes = ? WHERE id = ?|;
  2005. $sth = $dbh->prepare($query);
  2006. $sth->execute( $self->{intnotes}, $self->{id} ) || $self->dberror($query);
  2007. $dbh->commit;
  2008. }
  2009. =item $form->update_defaults($myconfig, $fld[, $dbh]);
  2010. Updates the defaults entry for the setting $fld following rules specified by
  2011. the existing value and returns the processed value that results. If $form is
  2012. false, such as the case when invoked as "Form::update_defaults('',...)", $dbh is
  2013. used as the handle. When $form is set, it uses $form->{dbh}, initialising the
  2014. connection if it does not yet exist. The entry $fld must exist prior to
  2015. executing this function and this update function does not handle the general
  2016. case of updating the defaults table.
  2017. B<NOTE>: rules handling is currently broken.
  2018. Rules followed by this function's processing:
  2019. =over
  2020. =item *
  2021. If digits are found in the field, increment the left-most set. This change,
  2022. unlike the others is reflected in the UPDATE.
  2023. =item *
  2024. Replace <?lsmb date ?> with the date specified in $form->{transdate} formatted
  2025. as $myconfig->{dateformat}.
  2026. =item *
  2027. Replace <?lsmb curr ?> with the value of $form->{currency}
  2028. =back
  2029. =cut
  2030. sub update_defaults {
  2031. my ( $self, $myconfig, $fld ) = @_;
  2032. if ( !$self->{dbh} && $self ) {
  2033. $self->db_init($myconfig);
  2034. }
  2035. my $dbh = $self->{dbh};
  2036. if ( !$self ) {
  2037. $dbh = $_[3];
  2038. }
  2039. my $query = qq|
  2040. SELECT value FROM defaults
  2041. WHERE setting_key = ? FOR UPDATE|;
  2042. $sth = $dbh->prepare($query);
  2043. $sth->execute($fld);
  2044. ($_) = $sth->fetchrow_array();
  2045. $_ = "0" unless $_;
  2046. # check for and replace
  2047. # <?lsmb DATE ?>, <?lsmb YYMMDD ?>, <?lsmb YEAR ?>, <?lsmb MONTH ?>, <?lsmb DAY ?> or variations of
  2048. # <?lsmb NAME 1 1 3 ?>, <?lsmb BUSINESS ?>, <?lsmb BUSINESS 10 ?>, <?lsmb CURR... ?>
  2049. # <?lsmb DESCRIPTION 1 1 3 ?>, <?lsmb ITEM 1 1 3 ?>, <?lsmb PARTSGROUP 1 1 3 ?> only for parts
  2050. # <?lsmb PHONE ?> for customer and vendors
  2051. my $num = $_;
  2052. ($num) = $num =~ /(\d+)/;
  2053. if ( defined $num ) {
  2054. my $incnum;
  2055. # if we have leading zeros check how long it is
  2056. if ( $num =~ /^0/ ) {
  2057. my $l = length $num;
  2058. $incnum = $num + 1;
  2059. $l -= length $incnum;
  2060. # pad it out with zeros
  2061. my $padzero = "0" x $l;
  2062. $incnum = ( "0" x $l ) . $incnum;
  2063. }
  2064. else {
  2065. $incnum = $num + 1;
  2066. }
  2067. s/$num/$incnum/;
  2068. }
  2069. my $dbvar = $_;
  2070. my $var = $_;
  2071. my $str;
  2072. my $param;
  2073. if (/<\?lsmb /) {
  2074. while (/<\?lsmb /) {
  2075. s/<\?lsmb .*? \?>//;
  2076. last unless $&;
  2077. $param = $&;
  2078. $str = "";
  2079. if ( $param =~ /<\?lsmb date \?>/i ) {
  2080. $str = (
  2081. $self->split_date(
  2082. $myconfig->{dateformat},
  2083. $self->{transdate}
  2084. )
  2085. )[0];
  2086. $var =~ s/$param/$str/;
  2087. }
  2088. if ( $param =~
  2089. /<\?lsmb (name|business|description|item|partsgroup|phone|custom)/i
  2090. )
  2091. {
  2092. #SC: XXX hairy, undoc, possibly broken
  2093. my $fld = lc $&;
  2094. $fld =~ s/<\?lsmb //;
  2095. if ( $fld =~ /name/ ) {
  2096. if ( $self->{type} ) {
  2097. $fld = $self->{vc};
  2098. }
  2099. }
  2100. my $p = $param;
  2101. $p =~ s/(<|>|%)//g;
  2102. my @p = split / /, $p;
  2103. my @n = split / /, uc $self->{$fld};
  2104. if ( $#p > 0 ) {
  2105. for ( my $i = 1 ; $i <= $#p ; $i++ ) {
  2106. $str .= substr( $n[ $i - 1 ], 0, $p[$i] );
  2107. }
  2108. }
  2109. else {
  2110. ($str) = split /--/, $self->{$fld};
  2111. }
  2112. $var =~ s/$param/$str/;
  2113. $var =~ s/\W//g if $fld eq 'phone';
  2114. }
  2115. if ( $param =~ /<\?lsmb (yy|mm|dd)/i ) {
  2116. # SC: XXX Does this even work anymore?
  2117. my $p = $param;
  2118. $p =~ s/(<|>|%)//g;
  2119. my $spc = $p;
  2120. $spc =~ s/\w//g;
  2121. $spc = substr( $spc, 0, 1 );
  2122. my %d = ( yy => 1, mm => 2, dd => 3 );
  2123. my @p = ();
  2124. my @a = $self->split_date( $myconfig->{dateformat},
  2125. $self->{transdate} );
  2126. for ( sort keys %d ) { push @p, $a[ $d{$_} ] if ( $p =~ /$_/ ) }
  2127. $str = join $spc, @p;
  2128. $var =~ s/$param/$str/;
  2129. }
  2130. if ( $param =~ /<\?lsmb curr/i ) {
  2131. $var =~ s/$param/$self->{currency}/;
  2132. }
  2133. }
  2134. }
  2135. $query = qq|
  2136. UPDATE defaults
  2137. SET value = ?
  2138. WHERE setting_key = ?|;
  2139. $sth = $dbh->prepare($query);
  2140. $sth->execute( $dbvar, $fld ) || $self->dberror($query);
  2141. $dbh->commit;
  2142. $var;
  2143. }
  2144. =item $form->db_prepare_vars(var1, var2, ..., varI<n>)
  2145. Undefines $form->{varI<m>}, 1 <= I<m> <= I<n>, iff $form-<{varI<m> is both
  2146. false and not "0".
  2147. =cut
  2148. sub db_prepare_vars {
  2149. my $self = shift;
  2150. for (@_) {
  2151. if ( !$self->{$_} and $self->{$_} ne "0" ) {
  2152. undef $self->{$_};
  2153. }
  2154. }
  2155. }
  2156. =item $form->split_date($dateformat[, $date]);
  2157. Returns ($rv, $yy, $mm, $dd) for the provided $date, or the current date if no
  2158. date is provided. $rv is a seperator-free merging of the fields $yy, $mm, and
  2159. $dd in the ordering supplied by $dateformat. If the supplied $date does not
  2160. contain non-digit characters, $rv is $date and the other return values are
  2161. undefined.
  2162. $yy is two digits.
  2163. =cut
  2164. sub split_date {
  2165. my ( $self, $dateformat, $date ) = @_;
  2166. my $mm;
  2167. my $dd;
  2168. my $yy;
  2169. my $rv;
  2170. if ( !$date ) {
  2171. my @d = localtime;
  2172. $dd = $d[3];
  2173. $mm = ++$d[4];
  2174. $yy = substr( $d[5], -2 );
  2175. $mm = substr( "0$mm", -2 );
  2176. $dd = substr( "0$dd", -2 );
  2177. }
  2178. if ( $dateformat =~ /^yy/ ) {
  2179. if ($date) {
  2180. if ( $date =~ /\D/ ) {
  2181. ( $yy, $mm, $dd ) = split /\D/, $date;
  2182. $mm *= 1;
  2183. $dd *= 1;
  2184. $mm = substr( "0$mm", -2 );
  2185. $dd = substr( "0$dd", -2 );
  2186. $yy = substr( $yy, -2 );
  2187. $rv = "$yy$mm$dd";
  2188. }
  2189. else {
  2190. $rv = $date;
  2191. }
  2192. }
  2193. else {
  2194. $rv = "$yy$mm$dd";
  2195. }
  2196. }
  2197. elsif ( $dateformat =~ /^mm/ ) {
  2198. if ($date) {
  2199. if ( $date =~ /\D/ ) {
  2200. ( $mm, $dd, $yy ) = split /\D/, $date;
  2201. $mm *= 1;
  2202. $dd *= 1;
  2203. $mm = substr( "0$mm", -2 );
  2204. $dd = substr( "0$dd", -2 );
  2205. $yy = substr( $yy, -2 );
  2206. $rv = "$mm$dd$yy";
  2207. }
  2208. else {
  2209. $rv = $date;
  2210. }
  2211. }
  2212. else {
  2213. $rv = "$mm$dd$yy";
  2214. }
  2215. }
  2216. elsif ( $dateformat =~ /^dd/ ) {
  2217. if ($date) {
  2218. if ( $date =~ /\D/ ) {
  2219. ( $dd, $mm, $yy ) = split /\D/, $date;
  2220. $mm *= 1;
  2221. $dd *= 1;
  2222. $mm = substr( "0$mm", -2 );
  2223. $dd = substr( "0$dd", -2 );
  2224. $yy = substr( $yy, -2 );
  2225. $rv = "$dd$mm$yy";
  2226. }
  2227. else {
  2228. $rv = $date;
  2229. }
  2230. }
  2231. else {
  2232. $rv = "$dd$mm$yy";
  2233. }
  2234. }
  2235. ( $rv, $yy, $mm, $dd );
  2236. }
  2237. =item $form->format_date($date);
  2238. Returns $date converted from 'yyyy-mm-dd' format to the format specified by
  2239. $form->{db_dateformat}. If the supplied date does not match /^\d{4}\D/,
  2240. return the supplied date.
  2241. This function takes a four digit year and returns the date with a four digit
  2242. year.
  2243. =cut
  2244. sub format_date {
  2245. # takes an iso date in, and converts it to the date for printing
  2246. my ( $self, $date ) = @_;
  2247. my $datestring;
  2248. if ( $date =~ /^\d{4}\D/ ) { # is an ISO date
  2249. $datestring = $self->{db_dateformat};
  2250. my ( $yyyy, $mm, $dd ) = split( /\W/, $date );
  2251. $datestring =~ s/y+/$yyyy/;
  2252. $datestring =~ s/mm/$mm/;
  2253. $datestring =~ s/dd/$dd/;
  2254. }
  2255. else { # return date
  2256. $datestring = $date;
  2257. }
  2258. $datestring;
  2259. }
  2260. =item $form->from_to($yyyy, $mm[, $interval]);
  2261. Returns the date $yyyy-$mm-01 and the the last day of the month interval - 1
  2262. months from then in the form ($form->format_date(fromdate),
  2263. $form->format_date(later)). If $interval is false but defined, the later date
  2264. is the current date.
  2265. This function dies horribly when $mm + $interval > 24
  2266. =cut
  2267. sub from_to {
  2268. my ( $self, $yyyy, $mm, $interval ) = @_;
  2269. my @t;
  2270. my $dd = 1;
  2271. my $fromdate = "$yyyy-${mm}-01";
  2272. my $bd = 1;
  2273. if ( defined $interval ) {
  2274. if ( $interval == 12 ) {
  2275. $yyyy++;
  2276. }
  2277. else {
  2278. if ( ( $mm += $interval ) > 12 ) {
  2279. $mm -= 12;
  2280. $yyyy++;
  2281. }
  2282. if ( $interval == 0 ) {
  2283. @t = localtime(time);
  2284. $dd = $t[3];
  2285. $mm = $t[4] + 1;
  2286. $yyyy = $t[5] + 1900;
  2287. $bd = 0;
  2288. }
  2289. }
  2290. }
  2291. else {
  2292. if ( ++$mm > 12 ) {
  2293. $mm -= 12;
  2294. $yyyy++;
  2295. }
  2296. }
  2297. $mm--;
  2298. @t = localtime( Time::Local::timelocal( 0, 0, 0, $dd, $mm, $yyyy ) - $bd );
  2299. $t[4]++;
  2300. $t[4] = substr( "0$t[4]", -2 );
  2301. $t[3] = substr( "0$t[3]", -2 );
  2302. $t[5] += 1900;
  2303. ( $self->format_date($fromdate), $self->format_date("$t[5]-$t[4]-$t[3]") );
  2304. }
  2305. sub audittrail {
  2306. my ( $self, $dbh, $myconfig, $audittrail ) = @_;
  2307. # table, $reference, $formname, $action, $id, $transdate) = @_;
  2308. my $query;
  2309. my $rv;
  2310. my $disconnect;
  2311. if ( !$dbh ) {
  2312. $dbh = $self->{dbh};
  2313. }
  2314. # if we have an id add audittrail, otherwise get a new timestamp
  2315. my @queryargs;
  2316. if ( $audittrail->{id} ) {
  2317. $query = qq|
  2318. SELECT value FROM defaults
  2319. WHERE setting_key = 'audittrail'|;
  2320. if ( $dbh->selectrow_array($query) ) {
  2321. my ( $null, $employee_id ) = $self->get_employee($dbh);
  2322. if ( $self->{audittrail} && !$myconfig ) {
  2323. chop $self->{audittrail};
  2324. my @a = split /\|/, $self->{audittrail};
  2325. my %newtrail = ();
  2326. my $key;
  2327. my $i;
  2328. my @flds = qw(tablename reference formname action transdate);
  2329. # put into hash and remove dups
  2330. while (@a) {
  2331. $key = "$a[2]$a[3]";
  2332. $i = 0;
  2333. $newtrail{$key} = { map { $_ => $a[ $i++ ] } @flds };
  2334. splice @a, 0, 5;
  2335. }
  2336. $query = qq|
  2337. INSERT INTO audittrail
  2338. (trans_id, tablename, reference,
  2339. formname, action, transdate,
  2340. employee_id)
  2341. VALUES (?, ?, ?, ?, ?, ?, ?)|;
  2342. my $sth = $dbh->prepare($query) || $self->dberror($query);
  2343. foreach $key (
  2344. sort {
  2345. $newtrail{$a}{transdate} cmp $newtrail{$b}{transdate}
  2346. } keys %newtrail
  2347. )
  2348. {
  2349. $i = 2;
  2350. $sth->bind_param( 1, $audittrail->{id} );
  2351. for (@flds) {
  2352. $sth->bind_param( $i++, $newtrail{$key}{$_} );
  2353. }
  2354. $sth->bind_param( $i++, $employee_id );
  2355. $sth->execute() || $self->dberror($query);
  2356. $sth->finish;
  2357. }
  2358. }
  2359. if ( $audittrail->{transdate} ) {
  2360. $query = qq|
  2361. INSERT INTO audittrail (
  2362. trans_id, tablename, reference,
  2363. formname, action, employee_id,
  2364. transdate)
  2365. VALUES (?, ?, ?, ?, ?, ?, ?)|;
  2366. @queryargs = (
  2367. $audittrail->{id}, $audittrail->{tablename},
  2368. $audittrail->{reference}, $audittrail->{formname},
  2369. $audittrail->{action}, $employee_id,
  2370. $audittrail->{transdate}
  2371. );
  2372. }
  2373. else {
  2374. $query = qq|
  2375. INSERT INTO audittrail
  2376. (trans_id, tablename, reference,
  2377. formname, action, employee_id)
  2378. VALUES (?, ?, ?, ?, ?, ?)|;
  2379. @queryargs = (
  2380. $audittrail->{id}, $audittrail->{tablename},
  2381. $audittrail->{reference}, $audittrail->{formname},
  2382. $audittrail->{action}, $employee_id,
  2383. );
  2384. }
  2385. $sth = $dbh->prepare($query);
  2386. $sth->execute(@queryargs) || $self->dberror($query);
  2387. }
  2388. }
  2389. else {
  2390. $query = qq|SELECT current_timestamp|;
  2391. my ($timestamp) = $dbh->selectrow_array($query);
  2392. $rv =
  2393. "$audittrail->{tablename}|$audittrail->{reference}|$audittrail->{formname}|$audittrail->{action}|$timestamp|";
  2394. }
  2395. $rv;
  2396. }
  2397. 1;
  2398. =back