summaryrefslogtreecommitdiff
path: root/LedgerSMB.pm
blob: 7f1306ff8c65a96b910d2fe1b397e307b1411586 (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. package LedgerSMB;
  38. sub new {
  39. my $type = shift;
  40. my $argstr = shift;
  41. read(STDIN, $_, $ENV{CONTENT_LENGTH});
  42. if ($argstr){
  43. $_ = $argstr;
  44. }
  45. elsif ($ENV{QUERY_STRING}) {
  46. $_ = $ENV{QUERY_STRING};
  47. }
  48. elsif ($ARGV[0]) {
  49. $_ = $ARGV[0];
  50. }
  51. my $self = {};
  52. %$self = split /[&=]/;
  53. for (keys %$self) { $self->{$_} = unescape("", $self->{$_}) }
  54. if (substr($self->{action}, 0, 1) !~ /( |\.)/) {
  55. $self->{action} = lc $self->{action};
  56. $self->{action} =~ s/( |-|,|\#|\/|\.$)/_/g;
  57. }
  58. $self->{menubar} = 1 if $self->{path} =~ /lynx/i;
  59. #menubar will be deprecated, replaced with below
  60. $self->{lynx} = 1 if $self->{path} =~ /lynx/i;
  61. $self->{version} = "1.2.0 Beta 2";
  62. $self->{dbversion} = "1.2.0";
  63. bless $self, $type;
  64. }
  65. sub debug {
  66. my ($self, $file) = @_;
  67. if ($file) {
  68. open(FH, '>', "$file") or die $!;
  69. for (sort keys %$self) { print FH "$_ = $self->{$_}\n" }
  70. close(FH);
  71. } else {
  72. print "\n";
  73. for (sort keys %$self) { print "$_ = $self->{$_}\n" }
  74. }
  75. }
  76. sub escape {
  77. my ($self, $str, $beenthere) = @_;
  78. # for Apache 2 we escape strings twice
  79. if (($ENV{SERVER_SIGNATURE} =~ /Apache\/2\.(\d+)\.(\d+)/) && !$beenthere) {
  80. $str = $self->escape($str, 1) if $1 == 0 && $2 < 44;
  81. }
  82. $str =~ s/([^a-zA-Z0-9_.-])/sprintf("%%%02x", ord($1))/ge;
  83. $str;
  84. }
  85. sub unescape {
  86. my ($self, $str) = @_;
  87. $str =~ tr/+/ /;
  88. $str =~ s/\\$//;
  89. $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
  90. $str =~ s/\r?\n/\n/g;
  91. $str;
  92. }
  93. sub numtextrows {
  94. my ($self, $str, $cols, $maxrows) = @_;
  95. my $rows = 0;
  96. for (split /\n/, $str) {
  97. $rows += int (((length) - 2)/$cols) + 1
  98. }
  99. $maxrows = $rows unless defined $maxrows;
  100. return ($rows > $maxrows) ? $maxrows : $rows;
  101. }
  102. sub isblank {
  103. my ($self, $name, $msg) = @_;
  104. $self->error($msg) if $self->{$name} =~ /^\s*$/;
  105. }
  106. sub redirect {
  107. my ($self, $msg) = @_;
  108. use List::Util qw(first);
  109. if ($self->{callback} || !$msg) {
  110. main::redirect();
  111. } else {
  112. $self->info($msg);
  113. }
  114. }
  115. sub format_string {
  116. my ($self, @fields) = @_;
  117. my $format = $self->{format};
  118. if ($self->{format} =~ /(postscript|pdf)/) {
  119. $format = 'tex';
  120. }
  121. my %replace = (
  122. 'order' => {
  123. html => [ '<', '>', '\n', '\r' ],
  124. txt => [ '\n', '\r' ],
  125. tex => [ quotemeta('\\'), '&', '\n','\r',
  126. '\$', '%', '_', '#',
  127. quotemeta('^'), '{', '}', '<', '>', '£'
  128. ] },
  129. html => { '<' => '&lt;', '>' => '&gt;','\n' => '<br />',
  130. '\r' => '<br />' },
  131. txt => { '\n' => "\n", '\r' => "\r" },
  132. tex => {'&' => '\&', '$' => '\$', '%' => '\%', '_' => '\_',
  133. '#' => '\#', quotemeta('^') => '\^\\', '{' => '\{',
  134. '}' => '\}', '<' => '$<$', '>' => '$>$',
  135. '\n' => '\newline ', '\r' => '\newline ',
  136. '£' => '\pounds ', quotemeta('\\') => '/'}
  137. );
  138. my $key;
  139. foreach $key (@{ $replace{order}{$format} }) {
  140. for (@fields) { $self->{$_} =~ s/$key/$replace{$format}{$key}/g }
  141. }
  142. }
  143. sub format_amount {
  144. my ($self, $myconfig, $amount, $places, $dash) = @_;
  145. my $negative ;
  146. if ($amount){
  147. $amount = $self->parse_amount($myconfig, $amount);
  148. $negative = ($amount < 0);
  149. $amount =~ s/-//;
  150. }
  151. if ($places =~ /\d+/) {
  152. #$places = 4 if $places == 2;
  153. $amount = $self->round_amount($amount, $places);
  154. }
  155. # is the amount negative
  156. # Parse $myconfig->{numberformat}
  157. my ($ts, $ds) = ($1, $2);
  158. if ($amount) {
  159. if ($myconfig->{numberformat}) {
  160. my ($whole, $dec) = split /\./, "$amount";
  161. $amount = join '', reverse split //, $whole;
  162. if ($places) {
  163. $dec .= "0" x $places;
  164. $dec = substr($dec, 0, $places);
  165. }
  166. if ($myconfig->{numberformat} eq '1,000.00') {
  167. $amount =~ s/\d{3,}?/$&,/g;
  168. $amount =~ s/,$//;
  169. $amount = join '', reverse split //, $amount;
  170. $amount .= "\.$dec" if ($dec ne "");
  171. }
  172. if ($myconfig->{numberformat} eq '1 000.00') {
  173. $amount =~ s/\d{3,}?/$& /g;
  174. $amount =~ s/\s$//;
  175. $amount = join '', reverse split //, $amount;
  176. $amount .= "\.$dec" if ($dec ne "");
  177. }
  178. if ($myconfig->{numberformat} eq "1'000.00") {
  179. $amount =~ s/\d{3,}?/$&'/g;
  180. $amount =~ s/'$//;
  181. $amount = join '', reverse split //, $amount;
  182. $amount .= "\.$dec" if ($dec ne "");
  183. }
  184. if ($myconfig->{numberformat} eq '1.000,00') {
  185. $amount =~ s/\d{3,}?/$&./g;
  186. $amount =~ s/\.$//;
  187. $amount = join '', reverse split //, $amount;
  188. $amount .= ",$dec" if ($dec ne "");
  189. }
  190. if ($myconfig->{numberformat} eq '1000,00') {
  191. $amount = "$whole";
  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 ($dash =~ /-/) {
  199. $amount = ($negative) ? "($amount)" : "$amount";
  200. } elsif ($dash =~ /DRCR/) {
  201. $amount = ($negative) ? "$amount DR" : "$amount CR";
  202. } else {
  203. $amount = ($negative) ? "-$amount" : "$amount";
  204. }
  205. }
  206. } else {
  207. if ($dash eq "0" && $places) {
  208. if ($myconfig->{numberformat} eq '1.000,00') {
  209. $amount = "0".","."0" x $places;
  210. } else {
  211. $amount = "0"."."."0" x $places;
  212. }
  213. } else {
  214. $amount = ($dash ne "") ? "$dash" : "";
  215. }
  216. }
  217. $amount;
  218. }
  219. sub parse_amount {
  220. my ($self, $myconfig, $amount) = @_;
  221. if ($amount eq '' or $amount == undef){
  222. return 0;
  223. }
  224. if (UNIVERSAL::isa($amount, 'Math::BigFloat')){ # Amount may not be an object
  225. return $amount;
  226. }
  227. my $numberformat = $myconfig->{numberformat};
  228. if (($numberformat eq '1.000,00') ||
  229. ($numberformat eq '1000,00')) {
  230. $amount =~ s/\.//g;
  231. $amount =~ s/,/./;
  232. }
  233. if ($numberformat eq '1 000.00'){
  234. $amount =~ s/\s//g;
  235. }
  236. if ($numberformat eq "1'000.00") {
  237. $amount =~ s/'//g;
  238. }
  239. $amount =~ s/,//g;
  240. if ($amount =~ s/\((\d*\.?\d*)\)/$1/){
  241. $amount = $1 * -1;
  242. }
  243. if ($amount =~ s/(\d*\.?\d*)\s?DR/$1/){
  244. $amount = $1 * -1;
  245. }
  246. $amount =~ s/\s?CR//;
  247. $amount = new Math::BigFloat($amount);
  248. return ($amount * 1);
  249. }
  250. sub round_amount {
  251. my ($self, $amount, $places) = @_;
  252. # These rounding rules follow from the previous implementation.
  253. # They should be changed to allow different rules for different accounts.
  254. Math::BigFloat->round_mode('+inf') if $amount >= 0;
  255. Math::BigFloat->round_mode('-inf') if $amount < 0;
  256. $amount = Math::BigFloat->new($amount)->ffround(-$places) if $places >= 0;
  257. $amount = Math::BigFloat->new($amount)->ffround(-($places-1)) if $places < 0;
  258. return $amount;
  259. }
  260. sub callproc {
  261. my $self = shift @_;
  262. my $procname = shift @_;
  263. my $argstr = "";
  264. my @results;
  265. for (1 .. scalar @_){
  266. $argstr .= "?, ";
  267. }
  268. $argstr =~ s/\, $//;
  269. $query = "SELECT * FROM $procname()";
  270. $query =~ s/\(\)/($argstr)/;
  271. my $sth = $self->{dbh}->prepare($query);
  272. $sth->execute(@_);
  273. while (my $ref = $sth->fetchrow_hashref(NAME_lc)){
  274. push @results, $ref;
  275. }
  276. @results;
  277. }
  278. sub datetonum {
  279. my ($self, $myconfig, $date, $picture) = @_;
  280. if ($date && $date =~ /\D/) {
  281. if ($myconfig->{dateformat} =~ /^yy/) {
  282. ($yy, $mm, $dd) = split /\D/, $date;
  283. }
  284. if ($myconfig->{dateformat} =~ /^mm/) {
  285. ($mm, $dd, $yy) = split /\D/, $date;
  286. }
  287. if ($myconfig->{dateformat} =~ /^dd/) {
  288. ($dd, $mm, $yy) = split /\D/, $date;
  289. }
  290. $dd *= 1;
  291. $mm *= 1;
  292. $yy += 2000 if length $yy == 2;
  293. $dd = substr("0$dd", -2);
  294. $mm = substr("0$mm", -2);
  295. $date = "$yy$mm$dd";
  296. }
  297. $date;
  298. }
  299. # Database routines used throughout
  300. sub db_init {
  301. my ($self, $myconfig) = @_;
  302. $self->{dbh} = $self->dbconnect_noauto($myconfig) || $self->dberror();
  303. my $query =
  304. "SELECT t.extends,
  305. coalesce (t.table_name, 'custom_' || extends)
  306. || ':' || f.field_name as field_def
  307. FROM custom_table_catalog t
  308. JOIN custom_field_catalog f USING (table_id)";
  309. my $sth = $self->{dbh}->prepare($query);
  310. $sth->execute;
  311. my $ref;
  312. while ($ref = $sth->fetchrow_hashref(NAME_lc)){
  313. push @{$self->{custom_db_fields}{$ref->{extends}}},
  314. $ref->{field_def};
  315. }
  316. }
  317. sub dbconnect_noauto {
  318. my ($self, $myconfig) = @_;
  319. # connect to database
  320. $dbh = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, {AutoCommit => 0}) or $self->dberror;
  321. # set db options
  322. if ($myconfig->{dboptions}) {
  323. $dbh->do($myconfig->{dboptions});
  324. }
  325. $dbh;
  326. }
  327. sub redo_rows {
  328. my ($self, $flds, $new, $count, $numrows) = @_;
  329. my @ndx = ();
  330. for (1 .. $count) {
  331. push @ndx, { num => $new->[$_-1]->{runningnumber}, ndx => $_ }
  332. }
  333. my $i = 0;
  334. # fill rows
  335. foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
  336. $i++;
  337. $j = $item->{ndx} - 1;
  338. for (@{$flds}) { $self->{"${_}_$i"} = $new->[$j]->{$_} }
  339. }
  340. # delete empty rows
  341. for $i ($count + 1 .. $numrows) {
  342. for (@{$flds}) { delete $self->{"${_}_$i"} }
  343. }
  344. }
  345. sub merge {
  346. my ($self, $src) = @_;
  347. for my $arg ($self, $src){
  348. shift;
  349. }
  350. my @keys;
  351. if (scalar @keys){
  352. @keys = @_;
  353. print "Keys: ". scalar @keys . "\n";
  354. }
  355. else {
  356. @keys = keys %{$src};
  357. print "Keys: ". scalar @keys . "\n";
  358. }
  359. for my $arg (keys %$src){
  360. $self->{$arg} = $src->{$arg};
  361. }
  362. }
  363. 1;