summaryrefslogtreecommitdiff
path: root/LedgerSMB.pm
blob: 4fe53d2e56c544f160aa807d82dc1af5f16430e7 (plain)
  1. #=====================================================================
  2. # LedgerSMB
  3. # Small Medium Business Accounting software
  4. # http://www.ledgersmb.org/
  5. #
  6. # Copyright (C) 2006
  7. # This work contains copyrighted information from a number of sources all used
  8. # with permission.
  9. #
  10. # This file contains source code included with or based on SQL-Ledger which
  11. # is Copyright Dieter Simader and DWS Systems Inc. 2000-2005 and licensed
  12. # under the GNU General Public License version 2 or, at your option, any later
  13. # version. For a full list including contact information of contributors,
  14. # maintainers, and copyright holders, see the CONTRIBUTORS file.
  15. #
  16. # Original Copyright Notice from SQL-Ledger 2.6.17 (before the fork):
  17. # Copyright (C) 2000
  18. #
  19. # Author: DWS Systems Inc.
  20. # Web: http://www.sql-ledger.org
  21. #
  22. # Contributors: Thomas Bayen <bayen@gmx.de>
  23. # Antti Kaihola <akaihola@siba.fi>
  24. # Moritz Bunkus (tex)
  25. # Jim Rawlings <jim@your-dba.com> (DB2)
  26. #======================================================================
  27. #
  28. # This file has undergone whitespace cleanup.
  29. #
  30. #======================================================================
  31. #
  32. # main package
  33. #
  34. #======================================================================
  35. use Math::BigFloat lib=>'GMP';
  36. use LedgerSMB::Sysconfig;
  37. use strict;
  38. package LedgerSMB;
  39. sub new {
  40. # This will probably be the last to be revised.
  41. my $type = shift;
  42. my $argstr = shift;
  43. read(STDIN, $_, $ENV{CONTENT_LENGTH});
  44. if ($argstr){
  45. $_ = $argstr;
  46. }
  47. elsif ($ENV{QUERY_STRING}) {
  48. $_ = $ENV{QUERY_STRING};
  49. }
  50. elsif ($ARGV[0]) {
  51. $_ = $ARGV[0];
  52. }
  53. my $self = {};
  54. %$self = split /[&=]/;
  55. for (keys %$self) { $self->{$_} = unescape("", $self->{$_}) }
  56. if (substr($self->{action}, 0, 1) !~ /( |\.)/) {
  57. $self->{action} = lc $self->{action};
  58. $self->{action} =~ s/( |-|,|\#|\/|\.$)/_/g;
  59. }
  60. $self->{menubar} = 1 if $self->{path} =~ /lynx/i;
  61. #menubar will be deprecated, replaced with below
  62. $self->{lynx} = 1 if $self->{path} =~ /lynx/i;
  63. $self->{version} = "1.2.0 Beta 2";
  64. $self->{dbversion} = "1.2.0";
  65. bless $self, $type;
  66. }
  67. sub debug {
  68. # Use Data Dumper for this one.
  69. my ($self, $file) = @_;
  70. if ($file) {
  71. open(FH, '>', "$file") or die $!;
  72. for (sort keys %$self) { print FH "$_ = $self->{$_}\n" }
  73. close(FH);
  74. } else {
  75. print "\n";
  76. for (sort keys %$self) { print "$_ = $self->{$_}\n" }
  77. }
  78. }
  79. sub escape {
  80. my ($self, $str) = @_;
  81. my $regex = qr/([^a-zA-Z0-9_.-])/;
  82. $str =~ s/$regex/sprintf("%%%02x", ord($1))/ge;
  83. # for Apache 2.0.x prior to 2.0.44 we escape strings twic;
  84. if ($ENV{SERVER_SIGNATURE} =~ /Apache\/2\.0\.(\d+)/ && $1 < 44) {
  85. $str =~ s/$regex/sprintf("%%%02x", ord($1))/ge;
  86. }
  87. $str;
  88. }
  89. sub unescape {
  90. my ($self, $str) = @_;
  91. $str =~ tr/+/ /;
  92. $str =~ s/\\$//;
  93. $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
  94. $str =~ s/\r?\n/\n/g;
  95. $str;
  96. }
  97. sub numtextrows {
  98. my ($self, $str, $cols, $maxrows) = @_;
  99. my $rows = 0;
  100. for (split /\n/, $str) {
  101. $rows += int (((length) - 2)/$cols) + 1
  102. }
  103. $maxrows = $rows unless defined $maxrows;
  104. return ($rows > $maxrows) ? $maxrows : $rows;
  105. }
  106. sub isblank {
  107. my ($self, $name, $msg) = @_;
  108. $self->error($msg) if $self->{$name} =~ /^\s*$/;
  109. }
  110. sub redirect {
  111. my ($self, $msg) = @_;
  112. use List::Util qw(first);
  113. if ($self->{callback} || !$msg) {
  114. main::redirect();
  115. } else {
  116. $self->info($msg);
  117. }
  118. }
  119. sub format_string {
  120. my ($self, @fields) = @_;
  121. my $format = $self->{format};
  122. if ($self->{format} =~ /(postscript|pdf)/) {
  123. $format = 'tex';
  124. }
  125. my %replace = (
  126. 'order' => {
  127. html => [ '<', '>', '\n', '\r' ],
  128. txt => [ '\n', '\r' ],
  129. tex => [ quotemeta('\\'), '&', '\n','\r',
  130. '\$', '%', '_', '#',
  131. quotemeta('^'), '{', '}', '<', '>', '£'
  132. ] },
  133. html => { '<' => '&lt;', '>' => '&gt;','\n' => '<br />',
  134. '\r' => '<br />' },
  135. txt => { '\n' => "\n", '\r' => "\r" },
  136. tex => {'&' => '\&', '$' => '\$', '%' => '\%', '_' => '\_',
  137. '#' => '\#', quotemeta('^') => '\^\\', '{' => '\{',
  138. '}' => '\}', '<' => '$<$', '>' => '$>$',
  139. '\n' => '\newline ', '\r' => '\newline ',
  140. '£' => '\pounds ', quotemeta('\\') => '/'}
  141. );
  142. my $key;
  143. foreach $key (@{ $replace{order}{$format} }) {
  144. for (@fields) { $self->{$_} =~ s/$key/$replace{$format}{$key}/g }
  145. }
  146. }
  147. sub format_amount {
  148. my ($self, $myconfig, $amount, $places, $dash) = @_;
  149. my $negative ;
  150. if ($amount){
  151. $amount = $self->parse_amount($myconfig, $amount);
  152. $negative = ($amount < 0);
  153. $amount =~ s/-//;
  154. }
  155. if ($places =~ /\d+/) {
  156. #$places = 4 if $places == 2;
  157. $amount = $self->round_amount($amount, $places);
  158. }
  159. # is the amount negative
  160. # Parse $myconfig->{numberformat}
  161. my ($ts, $ds) = ($1, $2);
  162. if ($amount) {
  163. if ($myconfig->{numberformat}) {
  164. my ($whole, $dec) = split /\./, "$amount";
  165. $amount = join '', reverse split //, $whole;
  166. if ($places) {
  167. $dec .= "0" x $places;
  168. $dec = substr($dec, 0, $places);
  169. }
  170. if ($myconfig->{numberformat} eq '1,000.00') {
  171. $amount =~ s/\d{3,}?/$&,/g;
  172. $amount =~ s/,$//;
  173. $amount = join '', reverse split //, $amount;
  174. $amount .= "\.$dec" if ($dec ne "");
  175. }
  176. if ($myconfig->{numberformat} eq '1 000.00') {
  177. $amount =~ s/\d{3,}?/$& /g;
  178. $amount =~ s/\s$//;
  179. $amount = join '', reverse split //, $amount;
  180. $amount .= "\.$dec" if ($dec ne "");
  181. }
  182. if ($myconfig->{numberformat} eq "1'000.00") {
  183. $amount =~ s/\d{3,}?/$&'/g;
  184. $amount =~ s/'$//;
  185. $amount = join '', reverse split //, $amount;
  186. $amount .= "\.$dec" if ($dec ne "");
  187. }
  188. if ($myconfig->{numberformat} eq '1.000,00') {
  189. $amount =~ s/\d{3,}?/$&./g;
  190. $amount =~ s/\.$//;
  191. $amount = join '', reverse split //, $amount;
  192. $amount .= ",$dec" if ($dec ne "");
  193. }
  194. if ($myconfig->{numberformat} eq '1000,00') {
  195. $amount = "$whole";
  196. $amount .= ",$dec" if ($dec ne "");
  197. }
  198. if ($myconfig->{numberformat} eq '1000.00') {
  199. $amount = "$whole";
  200. $amount .= ".$dec" if ($dec ne "");
  201. }
  202. if ($dash =~ /-/) {
  203. $amount = ($negative) ? "($amount)" : "$amount";
  204. } elsif ($dash =~ /DRCR/) {
  205. $amount = ($negative) ? "$amount DR" : "$amount CR";
  206. } else {
  207. $amount = ($negative) ? "-$amount" : "$amount";
  208. }
  209. }
  210. } else {
  211. if ($dash eq "0" && $places) {
  212. if ($myconfig->{numberformat} eq '1.000,00') {
  213. $amount = "0".","."0" x $places;
  214. } else {
  215. $amount = "0"."."."0" x $places;
  216. }
  217. } else {
  218. $amount = ($dash ne "") ? "$dash" : "";
  219. }
  220. }
  221. $amount;
  222. }
  223. sub parse_amount {
  224. my ($self, $myconfig, $amount) = @_;
  225. if ($amount eq '' or $amount == undef){
  226. return 0;
  227. }
  228. if (UNIVERSAL::isa($amount, 'Math::BigFloat')){ # Amount may not be an object
  229. return $amount;
  230. }
  231. my $numberformat = $myconfig->{numberformat};
  232. if (($numberformat eq '1.000,00') ||
  233. ($numberformat eq '1000,00')) {
  234. $amount =~ s/\.//g;
  235. $amount =~ s/,/./;
  236. }
  237. if ($numberformat eq '1 000.00'){
  238. $amount =~ s/\s//g;
  239. }
  240. if ($numberformat eq "1'000.00") {
  241. $amount =~ s/'//g;
  242. }
  243. $amount =~ s/,//g;
  244. if ($amount =~ s/\((\d*\.?\d*)\)/$1/){
  245. $amount = $1 * -1;
  246. }
  247. if ($amount =~ s/(\d*\.?\d*)\s?DR/$1/){
  248. $amount = $1 * -1;
  249. }
  250. $amount =~ s/\s?CR//;
  251. $amount = new Math::BigFloat($amount);
  252. return ($amount * 1);
  253. }
  254. sub round_amount {
  255. my ($self, $amount, $places) = @_;
  256. # These rounding rules follow from the previous implementation.
  257. # They should be changed to allow different rules for different accounts.
  258. Math::BigFloat->round_mode('+inf') if $amount >= 0;
  259. Math::BigFloat->round_mode('-inf') if $amount < 0;
  260. $amount = Math::BigFloat->new($amount)->ffround(-$places) if $places >= 0;
  261. $amount = Math::BigFloat->new($amount)->ffround(-($places-1)) if $places < 0;
  262. return $amount;
  263. }
  264. sub callproc {
  265. my $self = shift @_;
  266. my $procname = shift @_;
  267. my $argstr = "";
  268. my @results;
  269. for (1 .. scalar @_){
  270. $argstr .= "?, ";
  271. }
  272. $argstr =~ s/\, $//;
  273. my $query = "SELECT * FROM $procname()";
  274. $query =~ s/\(\)/($argstr)/;
  275. my $sth = $self->{dbh}->prepare($query);
  276. $sth->execute(@_);
  277. while (my $ref = $sth->fetchrow_hashref('NAME_lc')){
  278. push @results, $ref;
  279. }
  280. @results;
  281. }
  282. sub datetonum {
  283. my ($self, $myconfig, $date, $picture) = @_;
  284. my ($yy, $mm, $dd);
  285. if ($date && $date =~ /\D/) {
  286. if ($myconfig->{dateformat} =~ /^yy/) {
  287. ($yy, $mm, $dd) = split /\D/, $date;
  288. }
  289. if ($myconfig->{dateformat} =~ /^mm/) {
  290. ($mm, $dd, $yy) = split /\D/, $date;
  291. }
  292. if ($myconfig->{dateformat} =~ /^dd/) {
  293. ($dd, $mm, $yy) = split /\D/, $date;
  294. }
  295. $dd *= 1;
  296. $mm *= 1;
  297. $yy += 2000 if length $yy == 2;
  298. $dd = substr("0$dd", -2);
  299. $mm = substr("0$mm", -2);
  300. $date = "$yy$mm$dd";
  301. }
  302. $date;
  303. }
  304. # Database routines used throughout
  305. sub db_init {
  306. my ($self, $myconfig) = @_;
  307. $self->{dbh} = $self->dbconnect_noauto($myconfig) || $self->dberror();
  308. my $query =
  309. "SELECT t.extends,
  310. coalesce (t.table_name, 'custom_' || extends)
  311. || ':' || f.field_name as field_def
  312. FROM custom_table_catalog t
  313. JOIN custom_field_catalog f USING (table_id)";
  314. my $sth = $self->{dbh}->prepare($query);
  315. $sth->execute;
  316. my $ref;
  317. while ($ref = $sth->fetchrow_hashref('NAME_lc')){
  318. push @{$self->{custom_db_fields}{$ref->{extends}}},
  319. $ref->{field_def};
  320. }
  321. }
  322. sub dbconnect_noauto {
  323. my ($self, $myconfig) = @_;
  324. # connect to database
  325. my $dbh = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, {AutoCommit => 0}) or $self->dberror;
  326. # set db options
  327. if ($myconfig->{dboptions}) {
  328. $dbh->do($myconfig->{dboptions});
  329. }
  330. $dbh;
  331. }
  332. sub redo_rows {
  333. my ($self, $flds, $new, $count, $numrows) = @_;
  334. my @ndx = ();
  335. for (1 .. $count) {
  336. push @ndx, { num => $new->[$_-1]->{runningnumber}, ndx => $_ }
  337. }
  338. my $i = 0;
  339. my $j;
  340. # fill rows
  341. foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
  342. $i++;
  343. $j = $item->{ndx} - 1;
  344. for (@{$flds}) { $self->{"${_}_$i"} = $new->[$j]->{$_} }
  345. }
  346. # delete empty rows
  347. for $i ($count + 1 .. $numrows) {
  348. for (@{$flds}) { delete $self->{"${_}_$i"} }
  349. }
  350. }
  351. sub merge {
  352. my ($self, $src) = @_;
  353. for my $arg ($self, $src){
  354. shift;
  355. }
  356. my @keys;
  357. if (scalar @keys){
  358. @keys = @_;
  359. print "Keys: ". scalar @keys . "\n";
  360. }
  361. else {
  362. @keys = keys %{$src};
  363. print "Keys: ". scalar @keys . "\n";
  364. }
  365. for my $arg (keys %$src){
  366. $self->{$arg} = $src->{$arg};
  367. }
  368. }
  369. 1;