diff options
author | christopherm <christopherm@4979c152-3d1c-0410-bac9-87ea11338e46> | 2006-09-01 01:16:38 +0000 |
---|---|---|
committer | christopherm <christopherm@4979c152-3d1c-0410-bac9-87ea11338e46> | 2006-09-01 01:16:38 +0000 |
commit | ac5b087ea2d9ba7428d367aaeb288534158fee9a (patch) | |
tree | 2dbe0bdea0b653a215ba9ddfdf627cb57855050d /LedgerSMB |
Initial Import
git-svn-id: https://ledger-smb.svn.sourceforge.net/svnroot/ledger-smb/ledger-smb@1 4979c152-3d1c-0410-bac9-87ea11338e46
Diffstat (limited to 'LedgerSMB')
-rwxr-xr-x | LedgerSMB/AA.pm | 937 | ||||
-rwxr-xr-x | LedgerSMB/AM.pm | 1832 | ||||
-rwxr-xr-x | LedgerSMB/BP.pm | 326 | ||||
-rwxr-xr-x | LedgerSMB/CA.pm | 378 | ||||
-rwxr-xr-x | LedgerSMB/CP.pm | 684 | ||||
-rwxr-xr-x | LedgerSMB/CT.pm | 1080 | ||||
-rwxr-xr-x | LedgerSMB/Form.pm | 2942 | ||||
-rwxr-xr-x | LedgerSMB/GL.pm | 526 | ||||
-rwxr-xr-x | LedgerSMB/HR.pm | 555 | ||||
-rwxr-xr-x | LedgerSMB/IC.pm | 1714 | ||||
-rwxr-xr-x | LedgerSMB/IR.pm | 1123 | ||||
-rwxr-xr-x | LedgerSMB/IS.pm | 1684 | ||||
-rwxr-xr-x | LedgerSMB/Inifile.pm | 74 | ||||
-rwxr-xr-x | LedgerSMB/JC.pm | 582 | ||||
-rwxr-xr-x | LedgerSMB/Mailer.pm | 149 | ||||
-rwxr-xr-x | LedgerSMB/Menu.pm | 91 | ||||
-rwxr-xr-x | LedgerSMB/Num2text.pm | 149 | ||||
-rwxr-xr-x | LedgerSMB/OE.pm | 2238 | ||||
-rwxr-xr-x | LedgerSMB/OP.pm | 101 | ||||
-rwxr-xr-x | LedgerSMB/PE.pm | 1499 | ||||
-rwxr-xr-x | LedgerSMB/RC.pm | 391 | ||||
-rwxr-xr-x | LedgerSMB/RP.pm | 2103 | ||||
-rwxr-xr-x | LedgerSMB/Session.pm | 143 | ||||
-rwxr-xr-x | LedgerSMB/User.pm | 927 |
24 files changed, 22228 insertions, 0 deletions
diff --git a/LedgerSMB/AA.pm b/LedgerSMB/AA.pm new file mode 100755 index 00000000..92816650 --- /dev/null +++ b/LedgerSMB/AA.pm @@ -0,0 +1,937 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# AR/AP backend routines +# common routines +# +#====================================================================== + +package AA; + + +sub post_transaction { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + + my $null; + ($null, $form->{department_id}) = split(/--/, $form->{department}); + $form->{department_id} *= 1; + + my $ml = 1; + my $table = 'ar'; + my $buysell = 'buy'; + my $ARAP = 'AR'; + my $invnumber = "sinumber"; + my $keepcleared; + + if ($form->{vc} eq 'vendor') { + $table = 'ap'; + $buysell = 'sell'; + $ARAP = 'AP'; + $ml = -1; + $invnumber = "vinumber"; + } + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{exchangerate} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, $buysell); + + $form->{exchangerate} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{exchangerate}); + } + + my @taxaccounts = split / /, $form->{taxaccounts}; + my $tax = 0; + my $fxtax = 0; + my $amount; + my $diff; + + my %tax = (); + my $accno; + + # add taxes + foreach $accno (@taxaccounts) { + $fxtax += $tax{fxamount}{$accno} = $form->parse_amount($myconfig, $form->{"tax_$accno"}); + $tax += $tax{fxamount}{$accno}; + + push @{ $form->{acc_trans}{taxes} }, { + accno => $accno, + amount => $tax{fxamount}{$accno}, + project_id => 'NULL', + fx_transaction => 0 }; + + $amount = $tax{fxamount}{$accno} * $form->{exchangerate}; + $tax{amount}{$accno} = $form->round_amount($amount - $diff, 2); + $diff = $tax{amount}{$accno} - ($amount - $diff); + $amount = $tax{amount}{$accno} - $tax{fxamount}{$accno}; + $tax += $amount; + + if ($form->{currency} ne $form->{defaultcurrency}) { + push @{ $form->{acc_trans}{taxes} }, { + accno => $accno, + amount => $amount, + project_id => 'NULL', + fx_transaction => 1 }; + } + + } + + my %amount = (); + my $fxinvamount = 0; + for (1 .. $form->{rowcount}) { + $fxinvamount += $amount{fxamount}{$_} = $form->parse_amount($myconfig, $form->{"amount_$_"}) + } + + $form->{taxincluded} *= 1; + + my $i; + my $project_id; + my $cleared = 0; + + $diff = 0; + # deduct tax from amounts if tax included + for $i (1 .. $form->{rowcount}) { + + if ($amount{fxamount}{$i}) { + + if ($form->{taxincluded}) { + $amount = ($fxinvamount) ? $fxtax * $amount{fxamount}{$i} / $fxinvamount : 0; + $amount{fxamount}{$i} -= $amount; + } + + # multiply by exchangerate + $amount = $amount{fxamount}{$i} * $form->{exchangerate}; + $amount{amount}{$i} = $form->round_amount($amount - $diff, 2); + $diff = $amount{amount}{$i} - ($amount - $diff); + + ($null, $project_id) = split /--/, $form->{"projectnumber_$i"}; + $project_id ||= 'NULL'; + ($accno) = split /--/, $form->{"${ARAP}_amount_$i"}; + + if ($keepcleared) { + $cleared = ($form->{"cleared_$i"}) ? 1 : 0; + } + + push @{ $form->{acc_trans}{lineitems} }, { + accno => $accno, + amount => $amount{fxamount}{$i}, + project_id => $project_id, + description => $form->{"description_$i"}, + cleared => $cleared, + fx_transaction => 0 }; + + if ($form->{currency} ne $form->{defaultcurrency}) { + $amount = $amount{amount}{$i} - $amount{fxamount}{$i}; + push @{ $form->{acc_trans}{lineitems} }, { + accno => $accno, + amount => $amount, + project_id => $project_id, + description => $form->{"description_$i"}, + cleared => $cleared, + fx_transaction => 1 }; + } + } + } + + + my $invnetamount = 0; + for (@{ $form->{acc_trans}{lineitems} }) { $invnetamount += $_->{amount} } + my $invamount = $invnetamount + $tax; + + # adjust paidaccounts if there is no date in the last row + $form->{paidaccounts}-- unless ($form->{"datepaid_$form->{paidaccounts}"}); + + my $paid = 0; + my $fxamount; + + $diff = 0; + # add payments + for $i (1 .. $form->{paidaccounts}) { + $fxamount = $form->parse_amount($myconfig, $form->{"paid_$i"}); + + if ($fxamount) { + $paid += $fxamount; + + $paidamount = $fxamount * $form->{exchangerate}; + + $amount = $form->round_amount($paidamount - $diff, 2); + $diff = $amount - ($paidamount - $diff); + + $form->{datepaid} = $form->{"datepaid_$i"}; + + $paid{fxamount}{$i} = $fxamount; + $paid{amount}{$i} = $amount; + } + } + + $fxinvamount += $fxtax unless $form->{taxincluded}; + $fxinvamount = $form->round_amount($fxinvamount, 2); + $invamount = $form->round_amount($invamount, 2); + $paid = $form->round_amount($paid, 2); + + $paid = ($fxinvamount == $paid) ? $invamount : $form->round_amount($paid * $form->{exchangerate}, 2); + + $query = q|SELECT fxgain_accno_id, fxloss_accno_id + FROM defaults|; + + my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query); + + ($null, $form->{employee_id}) = split /--/, $form->{employee}; + unless ($form->{employee_id}) { + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + } + + # check if id really exists + if ($form->{id}) { + $keepcleared = 1; + $query = qq|SELECT id FROM $table + WHERE id = $form->{id}|; + + if ($dbh->selectrow_array($query)) { + # delete detail records + $query = qq|DELETE FROM acc_trans + WHERE trans_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + } + } else { + + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO $table (invnumber) + VALUES ('$uid')|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM $table + WHERE invnumber = '$uid'|; + + ($form->{id}) = $dbh->selectrow_array($query); + } + + + # record last payment date in ar/ap table + $form->{datepaid} = $form->{transdate} unless $form->{datepaid}; + my $datepaid = ($paid) ? qq|'$form->{datepaid}'| : 'NULL'; + + $form->{invnumber} = $form->update_defaults($myconfig, $invnumber) unless $form->{invnumber}; + + $query = qq|UPDATE $table SET invnumber = |.$dbh->quote($form->{invnumber}).qq|, + ordnumber = |.$dbh->quote($form->{ordnumber}).qq|, + transdate = '$form->{transdate}', + $form->{vc}_id = $form->{"$form->{vc}_id"}, + taxincluded = '$form->{taxincluded}', + amount = $invamount, + duedate = '$form->{duedate}', + paid = $paid, + datepaid = $datepaid, + netamount = $invnetamount, + curr = '$form->{currency}', + notes = |.$dbh->quote($form->{notes}).qq|, + department_id = $form->{department_id}, + employee_id = $form->{employee_id}, + ponumber = |.$dbh->quote($form->{ponumber}).qq| + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + # update exchangerate + my $buy = $form->{exchangerate}; + my $sell = 0; + if ($form->{vc} eq 'vendor') { + $buy = 0; + $sell = $form->{exchangerate}; + } + + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $buy, $sell); + } + + my $ref; + + # add individual transactions + foreach $ref (@{ $form->{acc_trans}{lineitems} }) { + + # insert detail records in acc_trans + if ($ref->{amount}) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, + project_id, memo, fx_transaction, cleared) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$ref->{accno}'), + $ref->{amount} * $ml, '$form->{transdate}', + $ref->{project_id}, |.$dbh->quote($ref->{description}).qq|, + '$ref->{fx_transaction}', '$ref->{cleared}')|; + + $dbh->do($query) || $form->dberror($query); + } + } + + # save taxes + foreach $ref (@{ $form->{acc_trans}{taxes} }) { + if ($ref->{amount}) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, fx_transaction) + VALUES ($form->{id}, + (SELECT id FROM chart + WHERE accno = '$ref->{accno}'), + $ref->{amount} * $ml, '$form->{transdate}', + '$ref->{fx_transaction}')|; + + $dbh->do($query) || $form->dberror($query); + } + } + + + my $arap; + + # record ar/ap + if (($arap = $invamount)) { + ($accno) = split /--/, $form->{$ARAP}; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate) + VALUES ($form->{id}, + (SELECT id FROM chart + WHERE accno = '$accno'), + $invamount * -1 * $ml, '$form->{transdate}')|; + + $dbh->do($query) || $form->dberror($query); + } + + # if there is no amount force ar/ap + if ($fxinvamount == 0) { + $arap = 1; + } + + + my $exchangerate; + + # add paid transactions + for $i (1 .. $form->{paidaccounts}) { + + if ($paid{fxamount}{$i}) { + + ($accno) = split(/--/, $form->{"${ARAP}_paid_$i"}); + $form->{"datepaid_$i"} = $form->{transdate} unless ($form->{"datepaid_$i"}); + + $exchangerate = 0; + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{"exchangerate_$i"} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, $buysell); + + $form->{"exchangerate_$i"} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{"exchangerate_$i"}); + } + + # if there is no amount + if ($fxinvamount == 0) { + $form->{exchangerate} = $form->{"exchangerate_$i"}; + } + + # ar/ap amount + if ($arap) { + ($accno) = split /--/, $form->{$ARAP}; + + # add ar/ap + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,transdate) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $paid{amount}{$i} * $ml, '$form->{"datepaid_$i"}')|; + + $dbh->do($query) || $form->dberror($query); + } + + $arap = $paid{amount}{$i}; + + + # add payment + if ($paid{fxamount}{$i}) { + + ($accno) = split /--/, $form->{"${ARAP}_paid_$i"}; + + my $cleared = ($form->{"cleared_$i"}) ? 1 : 0; + + $amount = $paid{fxamount}{$i}; + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, source, memo, cleared) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount * -1 * $ml, '$form->{"datepaid_$i"}', | + .$dbh->quote($form->{"source_$i"}).qq|, | + .$dbh->quote($form->{"memo_$i"}).qq|, '$cleared')|; + + $dbh->do($query) || $form->dberror($query); + + if ($form->{currency} ne $form->{defaultcurrency}) { + + # exchangerate gain/loss + $amount = ($form->round_amount($paid{fxamount}{$i} * $form->{exchangerate},2) - $form->round_amount($paid{fxamount}{$i} * $form->{"exchangerate_$i"},2)) * -1; + + if ($amount) { + + my $accno_id = (($amount * $ml) > 0) ? $fxgain_accno_id : $fxloss_accno_id; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, fx_transaction, cleared) + VALUES ($form->{id}, $accno_id, + $amount * $ml, '$form->{"datepaid_$i"}', '1', + '$cleared')|; + + $dbh->do($query) || $form->dberror($query); + } + + # exchangerate difference + $amount = $paid{amount}{$i} - $paid{fxamount}{$i} + $amount; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, fx_transaction, cleared, source) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount * -1 * $ml, '$form->{"datepaid_$i"}', '1', + '$cleared', | + .$dbh->quote($form->{"source_$i"}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + + } + + # update exchangerate record + $buy = $form->{"exchangerate_$i"}; + $sell = 0; + + if ($form->{vc} eq 'vendor') { + $buy = 0; + $sell = $form->{"exchangerate_$i"}; + } + + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + $form->update_exchangerate($dbh, $form->{currency}, $form->{"datepaid_$i"}, $buy, $sell); + } + } + } + } + + # save printed and queued + $form->save_status($dbh); + + my %audittrail = ( tablename => $table, + reference => $form->{invnumber}, + formname => 'transaction', + action => 'posted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + $form->save_recurring($dbh, $myconfig); + + my $rc = $dbh->commit; + + $dbh->disconnect; + + $rc; + +} + + +sub delete_transaction { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $table = ($form->{vc} eq 'customer') ? 'ar' : 'ap'; + + my %audittrail = ( tablename => $table, + reference => $form->{invnumber}, + formname => 'transaction', + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $query = qq|DELETE FROM $table WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM acc_trans WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # get spool files + $query = qq|SELECT spoolfile + FROM status + WHERE trans_id = $form->{id} + AND spoolfile IS NOT NULL|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $spoolfile; + my @spoolfiles = (); + + while (($spoolfile) = $sth->fetchrow_array) { + push @spoolfiles, $spoolfile; + } + + $sth->finish; + + $query = qq|DELETE FROM status WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # commit + my $rc = $dbh->commit; + $dbh->disconnect; + + if ($rc) { + foreach $spoolfile (@spoolfiles) { + unlink "$spool/$spoolfile" if $spoolfile; + } + } + + $rc; +} + + + +sub transactions { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + my $null; + my $var; + my $paid = "a.paid"; + my $ml = 1; + my $ARAP = 'AR'; + my $table = 'ar'; + my $buysell = 'buy'; + my $acc_trans_join; + my $acc_trans_flds; + + if ($form->{vc} eq 'vendor') { + $ml = -1; + $ARAP = 'AP'; + $table = 'ap'; + $buysell = 'sell'; + } + + ($form->{transdatefrom}, $form->{transdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{outstanding}) { + $paid = qq|SELECT SUM(ac.amount) * -1 * $ml + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + WHERE ac.trans_id = a.id + AND (c.link LIKE '%${ARAP}_paid%' OR c.link = '')|; + $paid .= qq| + AND ac.transdate <= '$form->{transdateto}'| if $form->{transdateto}; + $form->{summary} = 1; + } + + + if (!$form->{summary}) { + $acc_trans_flds = qq|, c.accno, ac.source, + pr.projectnumber, ac.memo AS description, + ac.amount AS linetotal, + i.description AS linedescription|; + + $acc_trans_join = qq| JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart c ON (c.id = ac.chart_id) + LEFT JOIN project pr ON (pr.id = ac.project_id) + LEFT JOIN invoice i ON (i.id = ac.invoice_id)|; + } + + my $query = qq|SELECT a.id, a.invnumber, a.ordnumber, a.transdate, + a.duedate, a.netamount, a.amount, ($paid) AS paid, + a.invoice, a.datepaid, a.terms, a.notes, + a.shipvia, a.shippingpoint, e.name AS employee, vc.name, + a.$form->{vc}_id, a.till, m.name AS manager, a.curr, + ex.$buysell AS exchangerate, d.description AS department, + a.ponumber $acc_trans_flds + FROM $table a + JOIN $form->{vc} vc ON (a.$form->{vc}_id = vc.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + LEFT JOIN employee m ON (e.managerid = m.id) + LEFT JOIN exchangerate ex ON (ex.curr = a.curr + AND ex.transdate = a.transdate) + LEFT JOIN department d ON (a.department_id = d.id) + $acc_trans_join|; + + my %ordinal = ( id => 1, + invnumber => 2, + ordnumber => 3, + transdate => 4, + duedate => 5, + datepaid => 10, + shipvia => 13, + shippingpoint => 14, + employee => 15, + name => 16, + manager => 19, + curr => 20, + department => 22, + ponumber => 23, + accno => 24, + source => 25, + project => 26, + description => 27); + + + my @a = (transdate, invnumber, name); + push @a, "employee" if $form->{l_employee}; + push @a, "manager" if $form->{l_manager}; + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $where = "1 = 1"; + if ($form->{"$form->{vc}_id"}) { + $where .= qq| AND a.$form->{vc}_id = $form->{"$form->{vc}_id"}|; + } else { + if ($form->{$form->{vc}}) { + $var = $form->like(lc $form->{$form->{vc}}); + $where .= " AND lower(vc.name) LIKE '$var'"; + } + } + + for (qw(department employee)) { + if ($form->{$_}) { + ($null, $var) = split /--/, $form->{$_}; + $where .= " AND a.${_}_id = $var"; + } + } + + for (qw(invnumber ordnumber)) { + if ($form->{$_}) { + $var = $form->like(lc $form->{$_}); + $where .= " AND lower(a.$_) LIKE '$var'"; + $form->{open} = $form->{closed} = 0; + } + } + + for (qw(ponumber shipvia notes)) { + if ($form->{$_}) { + $var = $form->like(lc $form->{$_}); + $where .= " AND lower(a.$_) LIKE '$var'"; + } + } + + if ($form->{description}) { + if ($acc_trans_flds) { + $var = $form->like(lc $form->{description}); + $where .= " AND lower(ac.memo) LIKE '$var' + OR lower(i.description) LIKE '$var'"; + } else { + $where .= " AND a.id = 0"; + } + } + + if ($form->{source}) { + if ($acc_trans_flds) { + $var = $form->like(lc $form->{source}); + $where .= " AND lower(ac.source) LIKE '$var'"; + } else { + $where .= " AND a.id = 0"; + } + } + + + $where .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $where .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + if ($form->{open} || $form->{closed}) { + unless ($form->{open} && $form->{closed}) { + $where .= " AND a.amount != a.paid" if ($form->{open}); + $where .= " AND a.amount = a.paid" if ($form->{closed}); + } + } + + if ($form->{till} ne "") { + $where .= " AND a.invoice = '1' + AND a.till IS NOT NULL"; + + if ($myconfig->{role} eq 'user') { + $where .= " AND e.login = '$form->{login}'"; + } + } + + if ($form->{$ARAP}) { + my ($accno) = split /--/, $form->{$ARAP}; + + $where .= qq|AND a.id IN (SELECT ac.trans_id + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + WHERE a.id = ac.trans_id + AND c.accno = '$accno')|; + } + + if ($form->{description}) { + $var = $form->like(lc $form->{description}); + $where .= qq| AND (a.id IN (SELECT DISTINCT trans_id + FROM acc_trans + WHERE lower(memo) LIKE '$var') + OR a.id IN (SELECT DISTINCT trans_id + FROM invoice + WHERE lower(description) LIKE '$var'))|; + } + + $query .= "WHERE $where + ORDER BY $sortorder"; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{exchangerate} = 1 unless $ref->{exchangerate}; + + if ($ref->{linetotal} <= 0) { + $ref->{debit} = $ref->{linetotal} * -1; + $ref->{credit} = 0; + } else { + $ref->{debit} = 0; + $ref->{credit} = $ref->{linetotal}; + } + + if ($ref->{invoice}) { + $ref->{description} ||= $ref->{linedescription}; + } + + if ($form->{outstanding}) { + next if $form->round_amount($ref->{amount}, 2) == $form->round_amount($ref->{paid}, 2); + } + + push @{ $form->{transactions} }, $ref; + } + + $sth->finish; + $dbh->disconnect; +} + + +# this is used in IS, IR to retrieve the name +sub get_name { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $dateformat = $myconfig->{dateformat}; + + if ($myconfig->{dateformat} !~ /^y/) { + my @a = split /\W/, $form->{transdate}; + $dateformat .= "yy" if (length $a[2] > 2); + } + + if ($form->{transdate} !~ /\W/) { + $dateformat = 'yyyymmdd'; + } + + my $duedate; + + if ($myconfig->{dbdriver} eq 'DB2') { + $duedate = ($form->{transdate}) ? "date('$form->{transdate}') + c.terms DAYS" : "current_date + c.terms DAYS"; + } else { + $duedate = ($form->{transdate}) ? "to_date('$form->{transdate}', '$dateformat') + c.terms" : "current_date + c.terms"; + } + + $form->{"$form->{vc}_id"} *= 1; + # get customer/vendor + my $query = qq|SELECT c.name AS $form->{vc}, c.discount, c.creditlimit, c.terms, + c.email, c.cc, c.bcc, c.taxincluded, + c.address1, c.address2, c.city, c.state, + c.zipcode, c.country, c.curr AS currency, c.language_code, + $duedate AS duedate, c.notes AS intnotes, + b.discount AS tradediscount, b.description AS business, + e.name AS employee, e.id AS employee_id + FROM $form->{vc} c + LEFT JOIN business b ON (b.id = c.business_id) + LEFT JOIN employee e ON (e.id = c.employee_id) + WHERE c.id = $form->{"$form->{vc}_id"}|; + + my $sth = $dbh->prepare($query); + + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + + if ($form->{id}) { + for (qw(currency employee employee_id intnotes)) { delete $ref->{$_} } + } + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + my $buysell = ($form->{vc} eq 'customer') ? "buy" : "sell"; + + # if no currency use defaultcurrency + $form->{currency} = ($form->{currency}) ? $form->{currency} : $form->{defaultcurrency}; + $form->{exchangerate} = 0 if $form->{currency} eq $form->{defaultcurrency}; + + if ($form->{transdate} && ($form->{currency} ne $form->{defaultcurrency})) { + $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, $buysell); + } + + $form->{forex} = $form->{exchangerate}; + + # if no employee, default to login + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh) unless $form->{employee_id}; + + my $arap = ($form->{vc} eq 'customer') ? 'ar' : 'ap'; + my $ARAP = uc $arap; + + $form->{creditremaining} = $form->{creditlimit}; + $query = qq|SELECT SUM(amount - paid) + FROM $arap + WHERE $form->{vc}_id = $form->{"$form->{vc}_id"}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{creditremaining}) -= $sth->fetchrow_array; + + $sth->finish; + + $query = qq|SELECT o.amount, (SELECT e.$buysell FROM exchangerate e + WHERE e.curr = o.curr + AND e.transdate = o.transdate) + FROM oe o + WHERE o.$form->{vc}_id = $form->{"$form->{vc}_id"} + AND o.quotation = '0' + AND o.closed = '0'|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($amount, $exch) = $sth->fetchrow_array) { + $exch = 1 unless $exch; + $form->{creditremaining} -= $amount * $exch; + } + + $sth->finish; + + + # get shipto if we did not converted an order or invoice + if (!$form->{shipto}) { + + for (qw(shiptoname shiptoaddress1 shiptoaddress2 shiptocity + shiptostate shiptozipcode shiptocountry shiptocontact + shiptophone shiptofax shiptoemail)) { + delete $form->{$_} + } + + ## needs fixing (SELECT *) + $query = qq|SELECT * + FROM shipto + WHERE trans_id = $form->{"$form->{vc}_id"}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + } + + # get taxes + $query = qq|SELECT c.accno + FROM chart c + JOIN $form->{vc}tax ct ON (ct.chart_id = c.id) + WHERE ct.$form->{vc}_id = $form->{"$form->{vc}_id"}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my %tax; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $tax{$ref->{accno}} = 1; + } + + $sth->finish; + + my $where = qq|AND (t.validto >= '$form->{transdate}' OR t.validto IS NULL)| if $form->{transdate}; + + # get tax rates and description + $query = qq|SELECT c.accno, c.description, t.rate, t.taxnumber + FROM chart c + JOIN tax t ON (c.id = t.chart_id) + WHERE c.link LIKE '%${ARAP}_tax%' + $where + ORDER BY accno, validto|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{taxaccounts} = ""; + my %a = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + if ($tax{$ref->{accno}}) { + if (not exists $a{$ref->{accno}}) { + for (qw(rate description taxnumber)) { $form->{"$ref->{accno}_$_"} = $ref->{$_} } + $form->{taxaccounts} .= "$ref->{accno} "; + $a{$ref->{accno}} = 1; + } + } + } + + $sth->finish; + chop $form->{taxaccounts}; + + # setup last accounts used for this customer/vendor + if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) { + + $query = qq|SELECT c.accno, c.description, c.link, c.category, + ac.project_id, p.projectnumber, a.department_id, + d.description AS department + FROM chart c + JOIN acc_trans ac ON (ac.chart_id = c.id) + JOIN $arap a ON (a.id = ac.trans_id) + LEFT JOIN project p ON (ac.project_id = p.id) + LEFT JOIN department d ON (d.id = a.department_id) + WHERE a.$form->{vc}_id = $form->{"$form->{vc}_id"} + AND a.id IN (SELECT max(id) + FROM $arap + WHERE $form->{vc}_id = $form->{"$form->{vc}_id"})|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $i = 0; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{department} = $ref->{department}; + $form->{department_id} = $ref->{department_id}; + + if ($ref->{link} =~ /_amount/) { + $i++; + $form->{"$form->{ARAP}_amount_$i"} = "$ref->{accno}--$ref->{description}" if $ref->{accno}; + $form->{"projectnumber_$i"} = "$ref->{projectnumber}--$ref->{project_id}" if $ref->{project_id}; + } + + if ($ref->{link} eq $form->{ARAP}) { + $form->{$form->{ARAP}} = $form->{"$form->{ARAP}_1"} = "$ref->{accno}--$ref->{description}" if $ref->{accno}; + } + } + + $sth->finish; + $form->{rowcount} = $i if ($i && !$form->{type}); + } + + $dbh->disconnect; +} + +1; diff --git a/LedgerSMB/AM.pm b/LedgerSMB/AM.pm new file mode 100755 index 00000000..ed4477bf --- /dev/null +++ b/LedgerSMB/AM.pm @@ -0,0 +1,1832 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# Administration module +# Chart of Accounts +# template routines +# preferences +# +#====================================================================== + +package AM; + + +sub get_account { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, description, charttype, gifi_accno, + category, link, contra + FROM chart + WHERE id = $form->{id}|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # get default accounts + $query = qq|SELECT inventory_accno_id, income_accno_id, expense_accno_id, + fxgain_accno_id, fxloss_accno_id + FROM defaults|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # check if we have any transactions + $query = qq|SELECT trans_id FROM acc_trans + WHERE chart_id = $form->{id}|; + + ($form->{orphaned}) = $dbh->selectrow_array($query); + $form->{orphaned} = !$form->{orphaned}; + + $dbh->disconnect; + +} + + +sub save_account { + + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off AutoCommit + my $dbh = $form->dbconnect_noauto($myconfig); + + $form->{link} = ""; + foreach my $item ($form->{AR}, + $form->{AR_amount}, + $form->{AR_tax}, + $form->{AR_paid}, + $form->{AP}, + $form->{AP_amount}, + $form->{AP_tax}, + $form->{AP_paid}, + $form->{IC}, + $form->{IC_income}, + $form->{IC_sale}, + $form->{IC_expense}, + $form->{IC_cogs}, + $form->{IC_taxpart}, + $form->{IC_taxservice}) { + $form->{link} .= "${item}:" if ($item); + } + + chop $form->{link}; + + # strip blanks from accno + for (qw(accno gifi_accno)) { $form->{$_} =~ s/( |')//g } + + foreach my $item (qw(accno gifi_accno description)) { + $form->{$item} =~ s/-(-+)/-/g; + $form->{$item} =~ s/ ( )+/ /g; + } + + my $query; + my $sth; + + $form->{contra} *= 1; + + # if we have an id then replace the old record + if ($form->{id}) { + $query = qq|UPDATE chart SET accno = '$form->{accno}', + description = |.$dbh->quote($form->{description}).qq|, + charttype = '$form->{charttype}', + gifi_accno = '$form->{gifi_accno}', + category = '$form->{category}', + link = '$form->{link}', + contra = '$form->{contra}' + WHERE id = $form->{id}|; + } else { + $query = qq|INSERT INTO chart (accno, description, charttype, + gifi_accno, category, link, contra) + VALUES ('$form->{accno}',| + .$dbh->quote($form->{description}).qq|, + '$form->{charttype}', '$form->{gifi_accno}', + '$form->{category}', '$form->{link}', '$form->{contra}')|; + } + + $dbh->do($query) || $form->dberror($query); + + + $chart_id = $form->{id}; + + if (! $form->{id}) { + # get id from chart + $query = qq|SELECT id + FROM chart + WHERE accno = '$form->{accno}'|; + + ($chart_id) = $dbh->selectrow_array($query); + } + + if ($form->{IC_taxpart} || $form->{IC_taxservice} || $form->{AR_tax} || $form->{AP_tax}) { + + # add account if it doesn't exist in tax + $query = qq|SELECT chart_id + FROM tax + WHERE chart_id = $chart_id|; + + my ($tax_id) = $dbh->selectrow_array($query); + + # add tax if it doesn't exist + unless ($tax_id) { + $query = qq|INSERT INTO tax (chart_id, rate) + VALUES ($chart_id, 0)|; + + $dbh->do($query) || $form->dberror($query); + } + + } else { + + # remove tax + if ($form->{id}) { + $query = qq|DELETE FROM tax + WHERE chart_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + } + } + + # commit + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; +} + + + +sub delete_account { + + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off AutoCommit + my $dbh = $form->dbconnect_noauto($myconfig); + + ## needs fixing (SELECT *...) + my $query = qq|SELECT * + FROM acc_trans + WHERE chart_id = $form->{id}|; + + if ($dbh->selectrow_array($query)) { + $dbh->disconnect; + return; + } + + + # delete chart of account record + $query = qq|DELETE FROM chart + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + # set inventory_accno_id, income_accno_id, expense_accno_id to defaults + $query = qq|UPDATE parts + SET inventory_accno_id = (SELECT inventory_accno_id + FROM defaults) + WHERE inventory_accno_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|UPDATE parts + SET income_accno_id = (SELECT income_accno_id + FROM defaults) + WHERE income_accno_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|UPDATE parts + SET expense_accno_id = (SELECT expense_accno_id + FROM defaults) + WHERE expense_accno_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + foreach my $table (qw(partstax customertax vendortax tax)) { + $query = qq|DELETE FROM $table + WHERE chart_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + } + + # commit and redirect + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; +} + + +sub gifi_accounts { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, description + FROM gifi + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{ALL} }, $ref; + } + + $sth->finish; + + $dbh->disconnect; +} + + + +sub get_gifi { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, description + FROM gifi + WHERE accno = '$form->{accno}'|; + + ($form->{accno}, $form->{description}) = $dbh->selectrow_array($query); + + # check for transactions ## needs fixing (SELECT *...) + $query = qq|SELECT * + FROM acc_trans a + JOIN chart c ON (a.chart_id = c.id) + JOIN gifi g ON (c.gifi_accno = g.accno) + WHERE g.accno = '$form->{accno}'|; + + ($form->{orphaned}) = $dbh->selectrow_array($query); + $form->{orphaned} = !$form->{orphaned}; + + $dbh->disconnect; + +} + + +sub save_gifi { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{accno} =~ s/( |')//g; + + foreach my $item (qw(accno description)) { + $form->{$item} =~ s/-(-+)/-/g; + $form->{$item} =~ s/ ( )+/ /g; + } + + # id is the old account number! + if ($form->{id}) { + $query = qq|UPDATE gifi + SET accno = '$form->{accno}', + description = |.$dbh->quote($form->{description}).qq| + WHERE accno = '$form->{id}'|; + + } else { + $query = qq|INSERT INTO gifi (accno, description) + VALUES ('$form->{accno}',| + .$dbh->quote($form->{description}).qq|)|; + } + + $dbh->do($query) || $form->dberror; + $dbh->disconnect; + +} + + +sub delete_gifi { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # id is the old account number! + $query = qq|DELETE FROM gifi + WHERE accno = '$form->{id}'|; + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub warehouses { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->sort_order(); + my $query = qq|SELECT id, description + FROM warehouse + ORDER BY description $form->{direction}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{ALL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub get_warehouse { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT description + FROM warehouse + WHERE id = $form->{id}|; + + ($form->{description}) = $dbh->selectrow_array($query); + + # see if it is in use + $query = qq|SELECT * FROM inventory + WHERE warehouse_id = $form->{id}|; + + ($form->{orphaned}) = $dbh->selectrow_array($query); + $form->{orphaned} = !$form->{orphaned}; + + $dbh->disconnect; +} + + +sub save_warehouse { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{description} =~ s/-(-)+/-/g; + $form->{description} =~ s/ ( )+/ /g; + + if ($form->{id}) { + $query = qq|UPDATE warehouse + SET description = |.$dbh->quote($form->{description}).qq| + WHERE id = $form->{id}|; + } else { + $query = qq|INSERT INTO warehouse (description) + VALUES (|.$dbh->quote($form->{description}).qq|)|; + } + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub delete_warehouse { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $query = qq|DELETE FROM warehouse + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + + +sub departments { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->sort_order(); + my $query = qq|SELECT id, description, role + FROM department + ORDER BY description $form->{direction}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{ALL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + + +sub get_department { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT description, role + FROM department + WHERE id = $form->{id}|; + + ($form->{description}, $form->{role}) = $dbh->selectrow_array($query); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + # see if it is in use ## needs fixing (SELECT * ...) + $query = qq|SELECT * + FROM dpt_trans + WHERE department_id = $form->{id}|; + + ($form->{orphaned}) = $dbh->selectrow_array($query); + $form->{orphaned} = !$form->{orphaned}; + + $dbh->disconnect; +} + + +sub save_department { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{description} =~ s/-(-)+/-/g; + $form->{description} =~ s/ ( )+/ /g; + + if ($form->{id}) { + $query = qq|UPDATE department + SET description = |.$dbh->quote($form->{description}).qq|, + role = '$form->{role}' + WHERE id = $form->{id}|; + + } else { + $query = qq|INSERT INTO department (description, role) + VALUES (| .$dbh->quote($form->{description}).qq|, '$form->{role}')|; + } + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub delete_department { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $query = qq|DELETE FROM department + WHERE id = $form->{id}|; + + $dbh->do($query); + $dbh->disconnect; + +} + + +sub business { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->sort_order(); + my $query = qq|SELECT id, description, discount + FROM business + ORDER BY description $form->{direction}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{ALL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub get_business { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT description, discount + FROM business + WHERE id = $form->{id}|; + + ($form->{description}, $form->{discount}) = $dbh->selectrow_array($query); + $dbh->disconnect; + +} + + +sub save_business { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{description} =~ s/-(-)+/-/g; + $form->{description} =~ s/ ( )+/ /g; + $form->{discount} /= 100; + + if ($form->{id}) { + $query = qq|UPDATE business + SET description = |.$dbh->quote($form->{description}).qq|, + discount = $form->{discount} + WHERE id = $form->{id}|; + + } else { + $query = qq|INSERT INTO business (description, discount) + VALUES (| .$dbh->quote($form->{description}).qq|, $form->{discount})|; + } + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub delete_business { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $query = qq|DELETE FROM business + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub sic { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{sort} = "code" unless $form->{sort}; + my @a = qw(code description); + + my %ordinal = ( code => 1, + description => 3 ); + + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT code, sictype, description + FROM sic + ORDER BY $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{ALL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub get_sic { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT code, sictype, description + FROM sic + WHERE code = |.$dbh->quote($form->{code}); + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + $dbh->disconnect; + +} + + +sub save_sic { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + foreach my $item (qw(code description)) { + $form->{$item} =~ s/-(-)+/-/g; + } + + # if there is an id + if ($form->{id}) { + $query = qq|UPDATE sic + SET code = |.$dbh->quote($form->{code}).qq|, + sictype = '$form->{sictype}', + description = |.$dbh->quote($form->{description}).qq| + WHERE code = |.$dbh->quote($form->{id}); + + } else { + $query = qq|INSERT INTO sic (code, sictype, description) + VALUES (|.$dbh->quote($form->{code}).qq|, + '$form->{sictype}',| + .$dbh->quote($form->{description}).qq|)|; + + } + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub delete_sic { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $query = qq|DELETE FROM sic + WHERE code = |.$dbh->quote($form->{code}); + + $dbh->do($query); + $dbh->disconnect; + +} + + +sub language { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{sort} = "code" unless $form->{sort}; + my @a = qw(code description); + + my %ordinal = ( code => 1, + description => 2 ); + + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT code, description + FROM language + ORDER BY $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{ALL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub get_language { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + ## needs fixing (SELECT *...) + my $query = qq|SELECT * + FROM language + WHERE code = |.$dbh->quote($form->{code}); + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + $dbh->disconnect; + +} + + +sub save_language { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{code} =~ s/ //g; + + foreach my $item (qw(code description)) { + $form->{$item} =~ s/-(-)+/-/g; + $form->{$item} =~ s/ ( )+/-/g; + } + + # if there is an id + if ($form->{id}) { + $query = qq|UPDATE language + SET code = |.$dbh->quote($form->{code}).qq|, + description = |.$dbh->quote($form->{description}).qq| + WHERE code = |.$dbh->quote($form->{id}); + + } else { + $query = qq|INSERT INTO language (code, description) + VALUES (|.$dbh->quote($form->{code}).qq|,| + .$dbh->quote($form->{description}).qq|)|; + } + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub delete_language { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $query = qq|DELETE FROM language + WHERE code = |.$dbh->quote($form->{code}); + + $dbh->do($query) || $form->dberror($query); + $dbh->disconnect; + +} + + +sub recurring_transactions { + + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT curr FROM defaults|; + + my ($defaultcurrency) = $dbh->selectrow_array($query); + $defaultcurrency =~ s/:.*//g; + + $form->{sort} ||= "nextdate"; + my @a = ($form->{sort}); + my $sortorder = $form->sort_order(\@a); + + $query = qq|SELECT 'ar' AS module, 'ar' AS transaction, a.invoice, + n.name AS description, a.amount, + s.*, se.formname AS recurringemail, + sp.formname AS recurringprint, + s.nextdate - current_date AS overdue, 'customer' AS vc, + ex.buy AS exchangerate, a.curr, + (s.nextdate IS NULL OR s.nextdate > s.enddate) AS expired + FROM recurring s + JOIN ar a ON (a.id = s.id) + JOIN customer n ON (n.id = a.customer_id) + LEFT JOIN recurringemail se ON (se.id = s.id) + LEFT JOIN recurringprint sp ON (sp.id = s.id) + LEFT JOIN exchangerate ex ON (ex.curr = a.curr AND a.transdate = ex.transdate) + + UNION + + SELECT 'ap' AS module, 'ap' AS transaction, a.invoice, + n.name AS description, a.amount, + s.*, se.formname AS recurringemail, + sp.formname AS recurringprint, + s.nextdate - current_date AS overdue, 'vendor' AS vc, + ex.sell AS exchangerate, a.curr, + (s.nextdate IS NULL OR s.nextdate > s.enddate) AS expired + FROM recurring s + JOIN ap a ON (a.id = s.id) + JOIN vendor n ON (n.id = a.vendor_id) + LEFT JOIN recurringemail se ON (se.id = s.id) + LEFT JOIN recurringprint sp ON (sp.id = s.id) + LEFT JOIN exchangerate ex ON (ex.curr = a.curr AND a.transdate = ex.transdate) + + UNION + + SELECT 'gl' AS module, 'gl' AS transaction, FALSE AS invoice, + a.description, (SELECT SUM(ac.amount) + FROM acc_trans ac + WHERE ac.trans_id = a.id + AND ac.amount > 0) AS amount, + s.*, se.formname AS recurringemail, + sp.formname AS recurringprint, + s.nextdate - current_date AS overdue, '' AS vc, + '1' AS exchangerate, '$defaultcurrency' AS curr, + (s.nextdate IS NULL OR s.nextdate > s.enddate) AS expired + FROM recurring s + JOIN gl a ON (a.id = s.id) + LEFT JOIN recurringemail se ON (se.id = s.id) + LEFT JOIN recurringprint sp ON (sp.id = s.id) + + UNION + + SELECT 'oe' AS module, 'so' AS transaction, FALSE AS invoice, + n.name AS description, a.amount, + s.*, se.formname AS recurringemail, + sp.formname AS recurringprint, + s.nextdate - current_date AS overdue, 'customer' AS vc, + ex.buy AS exchangerate, a.curr, + (s.nextdate IS NULL OR s.nextdate > s.enddate) AS expired + FROM recurring s + JOIN oe a ON (a.id = s.id) + JOIN customer n ON (n.id = a.customer_id) + LEFT JOIN recurringemail se ON (se.id = s.id) + LEFT JOIN recurringprint sp ON (sp.id = s.id) + LEFT JOIN exchangerate ex ON (ex.curr = a.curr AND a.transdate = ex.transdate) + WHERE a.quotation = '0' + + UNION + + SELECT 'oe' AS module, 'po' AS transaction, FALSE AS invoice, + n.name AS description, a.amount, + s.*, se.formname AS recurringemail, + sp.formname AS recurringprint, + s.nextdate - current_date AS overdue, 'vendor' AS vc, + ex.sell AS exchangerate, a.curr, + (s.nextdate IS NULL OR s.nextdate > s.enddate) AS expired + FROM recurring s + JOIN oe a ON (a.id = s.id) + JOIN vendor n ON (n.id = a.vendor_id) + LEFT JOIN recurringemail se ON (se.id = s.id) + LEFT JOIN recurringprint sp ON (sp.id = s.id) + LEFT JOIN exchangerate ex ON (ex.curr = a.curr AND a.transdate = ex.transdate) + WHERE a.quotation = '0' + + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $id; + my $transaction; + my %e = (); + my %p = (); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + $ref->{exchangerate} ||= 1; + + if ($ref->{id} != $id) { + + if (%e) { + $form->{transactions}{$transaction}->[$i]->{recurringemail} = ""; + for (keys %e) { $form->{transactions}{$transaction}->[$i]->{recurringemail} .= "${_}:" } + chop $form->{transactions}{$transaction}->[$i]->{recurringemail}; + } + + if (%p) { + $form->{transactions}{$transaction}->[$i]->{recurringprint} = ""; + for (keys %p) { $form->{transactions}{$transaction}->[$i]->{recurringprint} .= "${_}:" } + chop $form->{transactions}{$transaction}->[$i]->{recurringprint}; + } + + %e = (); + %p = (); + + push @{ $form->{transactions}{$ref->{transaction}} }, $ref; + + $id = $ref->{id}; + $i = $#{ $form->{transactions}{$ref->{transaction}} }; + + } + + $transaction = $ref->{transaction}; + + $e{$ref->{recurringemail}} = 1 if $ref->{recurringemail}; + $p{$ref->{recurringprint}} = 1 if $ref->{recurringprint}; + + } + + $sth->finish; + + # this is for the last row + if (%e) { + $form->{transactions}{$transaction}->[$i]->{recurringemail} = ""; + for (keys %e) { $form->{transactions}{$transaction}->[$i]->{recurringemail} .= "${_}:" } + chop $form->{transactions}{$transaction}->[$i]->{recurringemail}; + } + + if (%p) { + $form->{transactions}{$transaction}->[$i]->{recurringprint} = ""; + for (keys %p) { $form->{transactions}{$transaction}->[$i]->{recurringprint} .= "${_}:" } + chop $form->{transactions}{$transaction}->[$i]->{recurringprint}; + } + + + $dbh->disconnect; + +} + +sub recurring_details { + + my ($self, $myconfig, $form, $id) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT s.*, ar.id AS arid, ar.invoice AS arinvoice, + ap.id AS apid, ap.invoice AS apinvoice, + ar.duedate - ar.transdate AS overdue, + ar.datepaid - ar.transdate AS paid, + oe.reqdate - oe.transdate AS req, + oe.id AS oeid, oe.customer_id, oe.vendor_id + FROM recurring s + LEFT JOIN ar ON (ar.id = s.id) + LEFT JOIN ap ON (ap.id = s.id) + LEFT JOIN oe ON (oe.id = s.id) + WHERE s.id = $id|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + $form->{vc} = "customer" if $ref->{customer_id}; + $form->{vc} = "vendor" if $ref->{vendor_id}; + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + $form->{invoice} = ($form->{arid} && $form->{arinvoice}); + $form->{invoice} = ($form->{apid} && $form->{apinvoice}) unless $form->{invoice}; + + $query = qq|SELECT * + FROM recurringemail + WHERE id = $id|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{recurringemail} = ""; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{recurringemail} .= "$ref->{formname}:$ref->{format}:"; + $form->{message} = $ref->{message}; + } + + $sth->finish; + + $query = qq|SELECT * + FROM recurringprint + WHERE id = $id|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{recurringprint} = ""; + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{recurringprint} .= "$ref->{formname}:$ref->{format}:$ref->{printer}:"; + } + + $sth->finish; + + chop $form->{recurringemail}; + chop $form->{recurringprint}; + + for (qw(arinvoice apinvoice)) { delete $form->{$_} } + + $dbh->disconnect; + +} + + +sub update_recurring { + + my ($self, $myconfig, $form, $id) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT nextdate, repeat, unit + FROM recurring + WHERE id = $id|; + + my ($nextdate, $repeat, $unit) = $dbh->selectrow_array($query); + + my %advance = ( 'Pg' => "(date '$nextdate' + interval '$repeat $unit')", + 'DB2' => qq|(date ('$nextdate') + "$repeat $unit")|,); + + $interval{Oracle} = $interval{PgPP} = $interval{Pg}; + + # check if it is the last date + $query = qq|SELECT $advance{$myconfig->{dbdriver}} > enddate + FROM recurring + WHERE id = $id|; + + my ($last_repeat) = $dbh->selectrow_array($query); + if ($last_repeat) { + $advance{$myconfig->{dbdriver}} = "NULL"; + } + + $query = qq|UPDATE recurring + SET nextdate = $advance{$myconfig->{dbdriver}} + WHERE id = $id|; + + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + +sub load_template { + + my ($self, $form) = @_; + + open(TEMPLATE, "$form->{file}") or $form->error("$form->{file} : $!"); + + while (<TEMPLATE>) { + $form->{body} .= $_; + } + + close(TEMPLATE); + +} + + +sub save_template { + + my ($self, $form) = @_; + + open(TEMPLATE, ">$form->{file}") or $form->error("$form->{file} : $!"); + + # strip
+ $form->{body} =~ s/\r//g; + print TEMPLATE $form->{body}; + + close(TEMPLATE); + +} + + +sub save_preferences { + + my ($self, $myconfig, $form, $memberfile, $userspath) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # update name + my $query = qq|UPDATE employee + SET name = |.$dbh->quote($form->{name}).qq|, + role = '$form->{role}' + WHERE login = '$form->{login}'|; + + $dbh->do($query) || $form->dberror($query); + + # get default currency + $query = qq|SELECT curr, businessnumber + FROM defaults|; + + ($form->{currency}, $form->{businessnumber}) = $dbh->selectrow_array($query); + $form->{currency} =~ s/:.*//; + + $dbh->disconnect; + + my $myconfig = new User "$memberfile", "$form->{login}"; + + foreach my $item (keys %$form) { + $myconfig->{$item} = $form->{$item}; + } + + $myconfig->{password} = $form->{new_password} if ($form->{old_password} ne $form->{new_password}); + + $myconfig->save_member($memberfile, $userspath); + + 1; + +} + + +sub save_defaults { + + my ($self, $myconfig, $form) = @_; + + for (qw(IC IC_income IC_expense FX_gain FX_loss)) { ($form->{$_}) = split /--/, $form->{$_} } + + my @a; + $form->{curr} =~ s/ //g; + for (split /:/, $form->{curr}) { push(@a, uc pack "A3", $_) if $_ } + $form->{curr} = join ':', @a; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + # save defaults + my $query = qq|UPDATE defaults + SET inventory_accno_id = (SELECT id + FROM chart + WHERE accno = '$form->{IC}'), + income_accno_id = (SELECT id + FROM chart + WHERE accno = '$form->{IC_income}'), + expense_accno_id = (SELECT id + FROM chart + WHERE accno = '$form->{IC_expense}'), + fxgain_accno_id = (SELECT id + FROM chart + WHERE accno = '$form->{FX_gain}'), + fxloss_accno_id = (SELECT id + FROM chart + WHERE accno = '$form->{FX_loss}'), + glnumber = '$form->{glnumber}', + sinumber = '$form->{sinumber}', + vinumber = '$form->{vinumber}', + sonumber = '$form->{sonumber}', + ponumber = '$form->{ponumber}', + sqnumber = '$form->{sqnumber}', + rfqnumber = '$form->{rfqnumber}', + partnumber = '$form->{partnumber}', + employeenumber = '$form->{employeenumber}', + customernumber = '$form->{customernumber}', + vendornumber = '$form->{vendornumber}', + projectnumber = '$form->{projectnumber}', + yearend = '$form->{yearend}', + curr = '$form->{curr}', + weightunit = |.$dbh->quote($form->{weightunit}).qq|, + businessnumber = |.$dbh->quote($form->{businessnumber}); + + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub defaultaccounts { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # get defaults from defaults table + my $query = qq|SELECT * FROM defaults|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $form->{defaults}{IC} = $form->{inventory_accno_id}; + $form->{defaults}{IC_income} = $form->{income_accno_id}; + $form->{defaults}{IC_sale} = $form->{income_accno_id}; + $form->{defaults}{IC_expense} = $form->{expense_accno_id}; + $form->{defaults}{IC_cogs} = $form->{expense_accno_id}; + $form->{defaults}{FX_gain} = $form->{fxgain_accno_id}; + $form->{defaults}{FX_loss} = $form->{fxloss_accno_id}; + + $sth->finish; + + $query = qq|SELECT id, accno, description, link + FROM chart + WHERE link LIKE '%IC%' + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $nkey; + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + foreach my $key (split(/:/, $ref->{link})) { + if ($key =~ /IC/) { + $nkey = $key; + + if ($key =~ /cogs/) { + $nkey = "IC_expense"; + } + + if ($key =~ /sale/) { + $nkey = "IC_income"; + } + + %{ $form->{accno}{$nkey}{$ref->{accno}} } = ( id => $ref->{id}, + description => $ref->{description} ); + } + } + } + + $sth->finish; + + + $query = qq|SELECT id, accno, description + FROM chart + WHERE (category = 'I' OR category = 'E') + AND charttype = 'A' + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + %{ $form->{accno}{FX_gain}{$ref->{accno}} } = ( id => $ref->{id}, + description => $ref->{description} ); + + %{ $form->{accno}{FX_loss}{$ref->{accno}} } = ( id => $ref->{id}, + description => $ref->{description} ); + } + + $sth->finish; + + $dbh->disconnect; + +} + + +sub taxes { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT c.id, c.accno, c.description, + t.rate * 100 AS rate, t.taxnumber, t.validto + FROM chart c + JOIN tax t ON (c.id = t.chart_id) + ORDER BY 3, 6|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{taxrates} }, $ref; + } + + $sth->finish; + + $dbh->disconnect; + +} + + +sub save_taxes { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query = qq|DELETE FROM tax|; + $dbh->do($query) || $form->dberror($query); + + foreach my $item (split / /, $form->{taxaccounts}) { + my ($chart_id, $i) = split /_/, $item; + my $rate = $form->parse_amount($myconfig, $form->{"taxrate_$i"}) / 100; + + $query = qq|INSERT INTO tax (chart_id, rate, taxnumber, validto) + VALUES ($chart_id, $rate, | + .$dbh->quote($form->{"taxnumber_$i"}).qq|, | + .$form->dbquote($form->{"validto_$i"}, SQL_DATE) + .qq|)|; + + $dbh->do($query) || $form->dberror($query); + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub backup { + + my ($self, $myconfig, $form, $userspath, $gzip) = @_; + + my $mail; + my $err; + + my @t = localtime(time); + $t[4]++; + $t[5] += 1900; + $t[3] = substr("0$t[3]", -2); + $t[4] = substr("0$t[4]", -2); + + my $boundary = time; + my $tmpfile = "$userspath/$boundary.$myconfig->{dbname}-$form->{dbversion}-$t[5]$t[4]$t[3].sql"; + my $out = $form->{OUT}; + $form->{OUT} = ">$tmpfile"; + + open(OUT, "$form->{OUT}") or $form->error("$form->{OUT} : $!"); + + # get sequences, functions and triggers + my @tables = (); + my @sequences = (); + my @functions = (); + my @triggers = (); + my @schema = (); + + # get dbversion from -tables.sql + my $file = "$myconfig->{dbdriver}-tables.sql"; + + open(FH, "sql/$file") or $form->error("sql/$file : $!"); + + my @a = <FH>; + close(FH); + + @dbversion = grep /defaults \(version\)/, @a; + + $dbversion = "@dbversion"; + $dbversion =~ /(\d+\.\d+\.\d+)/; + $dbversion = User::calc_version($1); + + opendir SQLDIR, "sql/." or $form->error($!); + @a = grep /$myconfig->{dbdriver}-upgrade-.*?\.sql$/, readdir SQLDIR; + closedir SQLDIR; + + my $mindb; + my $maxdb; + + foreach my $line (@a) { + + $upgradescript = $line; + $line =~ s/(^$myconfig->{dbdriver}-upgrade-|\.sql$)//g; + + ($mindb, $maxdb) = split /-/, $line; + $mindb = User::calc_version($mindb); + + next if $mindb < $dbversion; + + $maxdb = User::calc_version($maxdb); + + $upgradescripts{$maxdb} = $upgradescript; + } + + + $upgradescripts{$dbversion} = "$myconfig->{dbdriver}-tables.sql"; + $upgradescripts{functions} = "$myconfig->{dbdriver}-functions.sql"; + + if (-f "sql/$myconfig->{dbdriver}-custom_tables.sql") { + $upgradescripts{customtables} = "$myconfig->{dbdriver}-custom_tables.sql"; + } + + if (-f "sql/$myconfig->{dbdriver}-custom_functions.sql") { + $upgradescripts{customfunctions} = "$myconfig->{dbdriver}-custom_functions.sql"; + } + + foreach my $key (sort keys %upgradescripts) { + + $file = $upgradescripts{$key}; + + open(FH, "sql/$file") or $form->error("sql/$file : $!"); + + push @schema, qq|-- $file\n|; + + while (<FH>) { + + if (/create table (\w+)/i) { + push @tables, $1; + } + + if (/create sequence (\w+)/i) { + push @sequences, $1; + next; + } + + if (/end function/i) { + push @functions, $_; + $function = 0; + $temp = 0; + next; + } + + if (/create function /i) { + $function = 1; + } + + if ($function) { + push @functions, $_; + next; + } + + if (/end trigger/i) { + push @triggers, $_; + $trigger = 0; + next; + } + + if (/create trigger/i) { + $trigger = 1; + } + + if ($trigger) { + push @triggers, $_; + next; + } + + push @schema, $_ if $_ !~ /^(insert|--)/i; + + } + + close(FH); + + } + + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $today = scalar localtime; + + $myconfig->{dbhost} = 'localhost' unless $myconfig->{dbhost}; + + print OUT qq|-- LedgerSMB Backup + -- Dataset: $myconfig->{dbname} + -- Version: $form->{dbversion} + -- Host: $myconfig->{dbhost} + -- Login: $form->{login} + -- User: $myconfig->{name} + -- Date: $today + -- + |; + + + @tables = grep !/^temp/, @tables; + # drop tables and sequences + for (@tables) { print OUT qq|DROP TABLE $_;\n| } + + print OUT "--\n"; + + # triggers and index files are dropped with the tables + + # drop functions + foreach $item (@functions) { + if ($item =~ /create function (.*\))/i) { + print OUT qq|DROP FUNCTION $1;\n|; + } + } + + # create sequences + foreach $item (@sequences) { + + if ($myconfig->{dbdriver} eq 'DB2') { + $query = qq|SELECT NEXTVAL FOR $item FROM sysibm.sysdummy1|; + } else { + $query = qq|SELECT last_value FROM $item|; + } + + my ($id) = $dbh->selectrow_array($query); + + if ($myconfig->{dbdriver} eq 'DB2') { + print OUT qq|DROP SEQUENCE $item RESTRICT + CREATE SEQUENCE $item AS INTEGER START WITH $id INCREMENT BY 1 MAXVALUE 2147483647 MINVALUE 1 CACHE 5;\n|; + } else { + if ($myconfig->{dbdriver} eq 'Pg') { + print OUT qq|CREATE SEQUENCE $item; + SELECT SETVAL('$item', $id, FALSE);\n|; + } else { + print OUT qq|DROP SEQUENCE $item + CREATE SEQUENCE $item START $id;\n|; + } + } + } + + print OUT "--\n"; + + # add schema + print OUT @schema; + print OUT "\n"; + + print OUT qq|-- set options + $myconfig->{dboptions}; + -- + |; + + my $query; + my $sth; + my @arr; + my $fields; + + foreach $table (@tables) { + + $query = qq|SELECT * FROM $table|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|INSERT INTO $table (|; + $query .= join ',', (map { $sth->{NAME}->[$_] } (0 .. $sth->{NUM_OF_FIELDS} - 1)); + $query .= qq|) VALUES|; + + while (@arr = $sth->fetchrow_array) { + + $fields = "("; + + $fields .= join ',', map { $dbh->quote($_) } @arr; + $fields .= ")"; + + print OUT qq|$query $fields;\n|; + } + + $sth->finish; + } + + print OUT "--\n"; + + # functions + for (@functions) { print OUT $_ } + + # triggers + for (@triggers) { print OUT $_ } + + # add the index files + open(FH, "sql/$myconfig->{dbdriver}-indices.sql"); + @a = <FH>; + close(FH); + print OUT @a; + + close(OUT); + + $dbh->disconnect; + + # compress backup if gzip defined + my $suffix = ""; + + if ($gzip) { + my @args = split / /, $gzip; + my @s = @args; + + push @args, "$tmpfile"; + system(@args) == 0 or $form->error("$args[0] : $?"); + + shift @s; + my %s = @s; + $suffix = ${-S} || ".gz"; + $tmpfile .= $suffix; + } + + if ($form->{media} eq 'email') { + + use LedgerSMB::Mailer; + $mail = new Mailer; + + $mail->{to} = qq|"$myconfig->{name}" <$myconfig->{email}>|; + $mail->{from} = qq|"$myconfig->{name}" <$myconfig->{email}>|; + $mail->{subject} = "LedgerSMB Backup / $myconfig->{dbname}-$form->{dbversion}-$t[5]$t[4]$t[3].sql$suffix"; + @{ $mail->{attachments} } = ($tmpfile); + $mail->{version} = $form->{version}; + $mail->{fileid} = "$boundary."; + + $myconfig->{signature} =~ s/\\n/\n/g; + $mail->{message} = "-- \n$myconfig->{signature}"; + + $err = $mail->send($out); + } + + if ($form->{media} eq 'file') { + + open(IN, "$tmpfile") or $form->error("$tmpfile : $!"); + open(OUT, ">-") or $form->error("STDOUT : $!"); + + print OUT qq|Content-Type: application/file; + Content-Disposition: attachment; filename="$myconfig->{dbname}-$form->{dbversion}-$t[5]$t[4]$t[3].sql$suffix" + + |; + binmode(IN); + binmode(OUT); + + while (<IN>) { + print OUT $_; + } + + close(IN); + close(OUT); + + } + + unlink "$tmpfile"; + +} + + +sub closedto { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT closedto, revtrans, audittrail + FROM defaults|; + + ($form->{closedto}, $form->{revtrans}, $form->{audittrail}) = $dbh->selectrow_array($query); + + $dbh->disconnect; + +} + + +sub closebooks { + + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect_noauto($myconfig); + my $query = qq|UPDATE defaults SET|; + + if ($form->{revtrans}) { + $query .= qq| revtrans = '1'|; + } else { + $query .= qq| revtrans = '0'|; + } + + $query .= qq|, closedto = |.$form->dbquote($form->{closedto}, SQL_DATE); + + if ($form->{audittrail}) { + $query .= qq|, audittrail = '1'|; + } else { + $query .= qq|, audittrail = '0'|; + } + + # set close in defaults + $dbh->do($query) || $form->dberror($query); + + if ($form->{removeaudittrail}) { + $query = qq|DELETE FROM audittrail + WHERE transdate < '$form->{removeaudittrail}'|; + + $dbh->do($query) || $form->dberror($query); + } + + $dbh->commit; + $dbh->disconnect; + +} + + +sub earningsaccounts { + + my ($self, $myconfig, $form) = @_; + + my ($query, $sth, $ref); + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # get chart of accounts + $query = qq|SELECT accno,description + FROM chart + WHERE charttype = 'A' + AND category = 'Q' + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + $form->{chart} = ""; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{chart} }, $ref; + } + + $sth->finish; + $dbh->disconnect; +} + + +sub post_yearend { + + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off AutoCommit + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO gl (reference, employee_id) + VALUES ('$uid', (SELECT id FROM employee + WHERE login = '$form->{login}'))|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM gl + WHERE reference = '$uid'|; + + ($form->{id}) = $dbh->selectrow_array($query); + + $query = qq|UPDATE gl + SET reference = |.$dbh->quote($form->{reference}).qq|, + description = |.$dbh->quote($form->{description}).qq|, + notes = |.$dbh->quote($form->{notes}).qq|, + transdate = '$form->{transdate}', + department_id = 0 + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + my $amount; + my $accno; + + # insert acc_trans transactions + for my $i (1 .. $form->{rowcount}) { + # extract accno + ($accno) = split(/--/, $form->{"accno_$i"}); + $amount = 0; + + if ($form->{"credit_$i"}) { + $amount = $form->{"credit_$i"}; + } + + if ($form->{"debit_$i"}) { + $amount = $form->{"debit_$i"} * -1; + } + + + # if there is an amount, add the record + if ($amount) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, source) + VALUES ($form->{id}, (SELECT id + FROM chart + WHERE accno = '$accno'), + $amount, '$form->{transdate}', | + .$dbh->quote($form->{reference}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + } + } + + $query = qq|INSERT INTO yearend (trans_id, transdate) + VALUES ($form->{id}, '$form->{transdate}')|; + + $dbh->do($query) || $form->dberror($query); + + my %audittrail = ( tablename => 'gl', + reference => $form->{reference}, + formname => 'yearend', + action => 'posted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + # commit and redirect + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +1; diff --git a/LedgerSMB/BP.pm b/LedgerSMB/BP.pm new file mode 100755 index 00000000..c1feadc2 --- /dev/null +++ b/LedgerSMB/BP.pm @@ -0,0 +1,326 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# Batch printing module backend routines +# +#====================================================================== + +package BP; + + +sub get_vc { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my %arap = ( invoice => ['ar'], + packing_list => ['oe', 'ar'], + sales_order => ['oe'], + work_order => ['oe'], + pick_list => ['oe', 'ar'], + purchase_order => ['oe'], + bin_list => ['oe'], + sales_quotation => ['oe'], + request_quotation => ['oe'], + timecard => ['jcitems'],); + + my $query = ""; + my $sth; + my $n; + my $count; + my $item; + + foreach $item (@{ $arap{$form->{type}} }) { + $query = qq|SELECT count(*) + FROM (SELECT DISTINCT vc.id + FROM $form->{vc} vc, $item a, status s + WHERE a.$form->{vc}_id = vc.id + AND s.trans_id = a.id + AND s.formname = '$form->{type}' + AND s.spoolfile IS NOT NULL) AS total|; + + ($n) = $dbh->selectrow_array($query); + $count += $n; + } + + # build selection list + my $union = ""; + $query = ""; + + if ($count < $myconfig->{vclimit}) { + + foreach $item (@{ $arap{$form->{type}} }) { + $query .= qq| $union + SELECT DISTINCT vc.id, vc.name + FROM $item a + JOIN $form->{vc} vc ON (a.$form->{vc}_id = vc.id) + JOIN status s ON (s.trans_id = a.id) + WHERE s.formname = '$form->{type}' + AND s.spoolfile IS NOT NULL|; + $union = "UNION"; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{"all_$form->{vc}"} }, $ref; + } + + $sth->finish; + } + + $form->all_years($myconfig, $dbh); + $dbh->disconnect; + +} + + +sub get_spoolfiles { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $invnumber = "invnumber"; + my $item; + + my %arap = ( invoice => ['ar'], + packing_list => ['oe', 'ar'], + sales_order => ['oe'], + work_order => ['oe'], + pick_list => ['oe', 'ar'], + purchase_order => ['oe'], + bin_list => ['oe'], + sales_quotation => ['oe'], + request_quotation => ['oe'], + timecard => ['jc'],); + + ($form->{transdatefrom}, $form->{transdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{type} eq 'timecard') { + my $dateformat = $myconfig->{dateformat}; + $dateformat =~ s/yy/yyyy/; + $dateformat =~ s/yyyyyy/yyyy/; + + $invnumber = 'id'; + + $query = qq|SELECT j.id, e.name, j.id AS invnumber, + to_char(j.checkedin, '$dateformat') AS transdate, + '' AS ordnumber, '' AS quonumber, '0' AS invoice, + '$arap{$form->{type}}[0]' AS module, s.spoolfile + FROM jcitems j + JOIN employee e ON (e.id = j.employee_id) + JOIN status s ON (s.trans_id = j.id) + WHERE s.formname = '$form->{type}' + AND s.spoolfile IS NOT NULL|; + + if ($form->{"$form->{vc}_id"}) { + $query .= qq| AND j.$form->{vc}_id = $form->{"$form->{vc}_id"}|; + } else { + + if ($form->{$form->{vc}}) { + $item = $form->like(lc $form->{$form->{vc}}); + $query .= " AND lower(e.name) LIKE '$item'"; + } + } + + $query .= " AND j.checkedin >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $query .= " AND j.checkedin <= '$form->{transdateto}'" if $form->{transdateto}; + + } else { + + foreach $item (@{ $arap{$form->{type}} }) { + + $invoice = "a.invoice"; + $invnumber = "invnumber"; + + if ($item eq 'oe') { + $invnumber = "ordnumber"; + $invoice = "'0'"; + } + + $query .= qq| $union + SELECT a.id, vc.name, a.$invnumber AS invnumber, a.transdate, + a.ordnumber, a.quonumber, $invoice AS invoice, + '$item' AS module, s.spoolfile + FROM $item a, $form->{vc} vc, status s + WHERE s.trans_id = a.id + AND s.spoolfile IS NOT NULL + AND s.formname = '$form->{type}' + AND a.$form->{vc}_id = vc.id|; + + if ($form->{"$form->{vc}_id"}) { + $query .= qq| AND a.$form->{vc}_id = $form->{"$form->{vc}_id"}|; + } else { + + if ($form->{$form->{vc}} ne "") { + $item = $form->like(lc $form->{$form->{vc}}); + $query .= " AND lower(vc.name) LIKE '$item'"; + } + } + + if ($form->{invnumber} ne "") { + $item = $form->like(lc $form->{invnumber}); + $query .= " AND lower(a.invnumber) LIKE '$item'"; + } + + if ($form->{ordnumber} ne "") { + $item = $form->like(lc $form->{ordnumber}); + $query .= " AND lower(a.ordnumber) LIKE '$item'"; + } + + if ($form->{quonumber} ne "") { + $item = $form->like(lc $form->{quonumber}); + $query .= " AND lower(a.quonumber) LIKE '$item'"; + } + + $query .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $query .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + $union = "UNION"; + + } + } + + my %ordinal = ( 'name' => 2, + 'invnumber' => 3, + 'transdate' => 4, + 'ordnumber' => 5, + 'quonumber' => 6,); + + my @a = (); + push @a, ("transdate", "$invnumber", "name"); + my $sortorder = $form->sort_order(\@a, \%ordinal); + $query .= " ORDER by $sortorder"; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{SPOOL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub delete_spool { + + my ($self, $myconfig, $form, $spool) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my %audittrail; + + $query = qq|UPDATE status + SET spoolfile = NULL + WHERE spoolfile = ?|; + + my $sth = $dbh->prepare($query) || $form->dberror($query); + + foreach my $i (1 .. $form->{rowcount}) { + + if ($form->{"checked_$i"}) { + $sth->execute($form->{"spoolfile_$i"}) || $form->dberror($query); + $sth->finish; + + %audittrail = ( tablename => $form->{module}, + reference => $form->{"reference_$i"}, + formname => $form->{type}, + action => 'dequeued', + id => $form->{"id_$i"} ); + + $form->audittrail($dbh, "", \%audittrail); + } + } + + # commit + my $rc = $dbh->commit; + $dbh->disconnect; + + if ($rc) { + foreach my $i (1 .. $form->{rowcount}) { + $_ = qq|$spool/$form->{"spoolfile_$i"}|; + if ($form->{"checked_$i"}) { + unlink; + } + } + } + + $rc; +} + + +sub print_spool { + + my ($self, $myconfig, $form, $spool) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my %audittrail; + + my $query = qq|UPDATE status + SET printed = '1' + WHERE spoolfile = ?|; + + my $sth = $dbh->prepare($query) || $form->dberror($query); + + foreach my $i (1 .. $form->{rowcount}) { + + if ($form->{"checked_$i"}) { + open(OUT, $form->{OUT}) or $form->error("$form->{OUT} : $!"); + binmode(OUT); + + $spoolfile = qq|$spool/$form->{"spoolfile_$i"}|; + + # send file to printer + open(IN, $spoolfile) or $form->error("$spoolfile : $!"); + binmode(IN); + + while (<IN>) { + print OUT $_; + } + + close(IN); + close(OUT); + + $sth->execute($form->{"spoolfile_$i"}) || $form->dberror($query); + $sth->finish; + + %audittrail = ( tablename => $form->{module}, + reference => $form->{"reference_$i"}, + formname => $form->{type}, + action => 'printed', + id => $form->{"id_$i"} ); + + $form->audittrail($dbh, "", \%audittrail); + + $dbh->commit; + } + } + + $dbh->disconnect; + +} + + +1; + diff --git a/LedgerSMB/CA.pm b/LedgerSMB/CA.pm new file mode 100755 index 00000000..d6df616d --- /dev/null +++ b/LedgerSMB/CA.pm @@ -0,0 +1,378 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# chart of accounts +# +#====================================================================== + + +package CA; + + +sub all_accounts { + + my ($self, $myconfig, $form) = @_; + + my $amount = (); + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, SUM(acc_trans.amount) AS amount + FROM chart, acc_trans + WHERE chart.id = acc_trans.chart_id + GROUP BY accno|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $amount{$ref->{accno}} = $ref->{amount} + } + + $sth->finish; + + $query = qq|SELECT accno, description + FROM gifi|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $gifi = (); + + while (my ($accno, $description) = $sth->fetchrow_array) { + $gifi{$accno} = $description; + } + + $sth->finish; + + $query = qq|SELECT c.id, c.accno, c.description, c.charttype, + c.gifi_accno, c.category, c.link + FROM chart c + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ca = $sth->fetchrow_hashref(NAME_lc)) { + $ca->{amount} = $amount{$ca->{accno}}; + $ca->{gifi_description} = $gifi{$ca->{gifi_accno}}; + + if ($ca->{amount} < 0) { + $ca->{debit} = $ca->{amount} * -1; + } else { + $ca->{credit} = $ca->{amount}; + } + + push @{ $form->{CA} }, $ca; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub all_transactions { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # get chart_id + my $query = qq|SELECT id + FROM chart + WHERE accno = '$form->{accno}'|; + + if ($form->{accounttype} eq 'gifi') { + $query = qq|SELECT id + FROM chart + WHERE gifi_accno = '$form->{gifi_accno}'|; + } + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my @id = (); + + while (my ($id) = $sth->fetchrow_array) { + push @id, $id; + } + + $sth->finish; + + my $fromdate_where; + my $todate_where; + + ($form->{fromdate}, $form->{todate}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{fromdate}) { + $fromdate_where = qq| AND ac.transdate >= '$form->{fromdate}' |; + } + + if ($form->{todate}) { + $todate_where .= qq| AND ac.transdate <= '$form->{todate}' |; + } + + + my $false = ($myconfig->{dbdriver} =~ /Pg/) ? FALSE : q|'0'|; + + # Oracle workaround, use ordinal positions + my %ordinal = ( transdate => 4, + reference => 2, + description => 3 ); + + my @a = qw(transdate reference description); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $null; + my $department_id; + my $dpt_where; + my $dpt_join; + my $union; + + ($null, $department_id) = split /--/, $form->{department}; + + if ($department_id) { + $dpt_join = qq| JOIN department t ON (t.id = a.department_id) |; + $dpt_where = qq| AND t.id = $department_id |; + } + + + my $project; + my $project_id; + + if ($form->{projectnumber}) { + ($null, $project_id) = split /--/, $form->{projectnumber}; + $project = qq| AND ac.project_id = $project_id |; + } + + if ($form->{accno} || $form->{gifi_accno}) { + # get category for account + $query = qq|SELECT description, category, link, contra + FROM chart + WHERE accno = '$form->{accno}'|; + + if ($form->{accounttype} eq 'gifi') { + $query = qq|SELECT description, category, link, contra + FROM chart + WHERE gifi_accno = '$form->{gifi_accno}' + AND charttype = 'A'|; + } + + ($form->{description}, $form->{category}, $form->{link}, $form->{contra}) = $dbh->selectrow_array($query); + + if ($form->{fromdate}) { + + if ($department_id) { + + # get beginning balance + $query = ""; + $union = ""; + + for (qw(ar ap gl)) { + + if ($form->{accounttype} eq 'gifi') { + $query = qq| $union + SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN $_ a ON (a.id = ac.trans_id) + JOIN chart c ON (ac.chart_id = c.id) + WHERE c.gifi_accno = '$form->{gifi_accno}' + AND ac.transdate < '$form->{fromdate}' + AND a.department_id = $department_id + $project |; + + } else { + + $query .= qq| $union + SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN $_ a ON (a.id = ac.trans_id) + JOIN chart c ON (ac.chart_id = c.id) + WHERE c.accno = '$form->{accno}' + AND ac.transdate < '$form->{fromdate}' + AND a.department_id = $department_id + $project |; + } + + $union = qq| UNION ALL |; + } + + } else { + + if ($form->{accounttype} eq 'gifi') { + $query = qq|SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + WHERE c.gifi_accno = '$form->{gifi_accno}' + AND ac.transdate < '$form->{fromdate}' + $project |; + } else { + $query = qq|SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + WHERE c.accno = '$form->{accno}' + AND ac.transdate < '$form->{fromdate}' + $project |; + } + } + + ($form->{balance}) = $dbh->selectrow_array($query); + + } + } + + $query = ""; + $union = ""; + + foreach my $id (@id) { + + # get all transactions + $query .= qq|$union + SELECT a.id, a.reference, a.description, ac.transdate, + $false AS invoice, ac.amount, 'gl' as module, ac.cleared, + ac.source, '' AS till, ac.chart_id + FROM gl a + JOIN acc_trans ac ON (ac.trans_id = a.id) + $dpt_join + WHERE ac.chart_id = $id + $fromdate_where + $todate_where + $dpt_where + $project + + UNION ALL + + SELECT a.id, a.invnumber, c.name, ac.transdate, + a.invoice, ac.amount, 'ar' as module, ac.cleared, + ac.source, + a.till, ac.chart_id + FROM ar a + JOIN acc_trans ac ON (ac.trans_id = a.id) + JOIN customer c ON (a.customer_id = c.id) + $dpt_join + WHERE ac.chart_id = $id + $fromdate_where + $todate_where + $dpt_where + $project + + UNION ALL + + SELECT a.id, a.invnumber, v.name, ac.transdate, + a.invoice, ac.amount, 'ap' as module, ac.cleared, + ac.source, a.till, ac.chart_id + FROM ap a + JOIN acc_trans ac ON (ac.trans_id = a.id) + JOIN vendor v ON (a.vendor_id = v.id) + $dpt_join + WHERE ac.chart_id = $id + $fromdate_where + $todate_where + $dpt_where + $project |; + + $union = qq| UNION ALL |; + } + + $query .= qq| ORDER BY $sortorder |; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|SELECT c.id, c.accno + FROM chart c + JOIN acc_trans ac ON (ac.chart_id = c.id) + WHERE ac.amount >= 0 + AND (c.link = 'AR' OR c.link = 'AP') + AND ac.trans_id = ?|; + + my $dr = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|SELECT c.id, c.accno + FROM chart c + JOIN acc_trans ac ON (ac.chart_id = c.id) + WHERE ac.amount < 0 + AND (c.link = 'AR' OR c.link = 'AP') + AND ac.trans_id = ?|; + + my $cr = $dbh->prepare($query) || $form->dberror($query); + + my $accno; + my $chart_id; + my %accno; + + while (my $ca = $sth->fetchrow_hashref(NAME_lc)) { + + # gl + if ($ca->{module} eq "gl") { + $ca->{module} = "gl"; + } + + # ap + if ($ca->{module} eq "ap") { + $ca->{module} = ($ca->{invoice}) ? 'ir' : 'ap'; + $ca->{module} = 'ps' if $ca->{till}; + } + + # ar + if ($ca->{module} eq "ar") { + $ca->{module} = ($ca->{invoice}) ? 'is' : 'ar'; + $ca->{module} = 'ps' if $ca->{till}; + } + + if ($ca->{amount}) { + %accno = (); + + if ($ca->{amount} < 0) { + $ca->{debit} = $ca->{amount} * -1; + $ca->{credit} = 0; + $dr->execute($ca->{id}); + $ca->{accno} = (); + + while (($chart_id, $accno) = $dr->fetchrow_array) { + $accno{$accno} = 1 if $chart_id ne $ca->{chart_id}; + } + + $dr->finish; + + for (sort keys %accno) { push @{ $ca->{accno} }, "$_ " } + + } else { + + $ca->{credit} = $ca->{amount}; + $ca->{debit} = 0; + + $cr->execute($ca->{id}); + $ca->{accno} = (); + + while (($chart_id, $accno) = $cr->fetchrow_array) { + $accno{$accno} = 1 if $chart_id ne $ca->{chart_id}; + } + + $cr->finish; + + for (keys %accno) { push @{ $ca->{accno} }, "$_ " } + + } + + push @{ $form->{CA} }, $ca; + } + } + + $sth->finish; + $dbh->disconnect; + +} + +1; + diff --git a/LedgerSMB/CP.pm b/LedgerSMB/CP.pm new file mode 100755 index 00000000..897783fe --- /dev/null +++ b/LedgerSMB/CP.pm @@ -0,0 +1,684 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# Check and receipt printing payment module backend routines +# Number to text conversion routines are in +# locale/{countrycode}/Num2text +# +#====================================================================== + +package CP; + + +sub new { + + my ($type, $countrycode) = @_; + + $self = {}; + + if ($countrycode) { + + if (-f "locale/$countrycode/Num2text") { + require "locale/$countrycode/Num2text"; + } else { + use LedgerSMB::Num2text; + } + + } else { + use LedgerSMB::Num2text; + } + + bless $self, $type; + +} + + +sub paymentaccounts { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, description, link + FROM chart + WHERE link LIKE '%$form->{ARAP}%' + ORDER BY accno|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{PR}{$form->{ARAP}} = (); + $form->{PR}{"$form->{ARAP}_paid"} = (); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + foreach my $item (split /:/, $ref->{link}) { + + if ($item eq $form->{ARAP}) { + push @{ $form->{PR}{$form->{ARAP}} }, $ref; + } + + if ($item eq "$form->{ARAP}_paid") { + push @{ $form->{PR}{"$form->{ARAP}_paid"} }, $ref; + } + } + } + + $sth->finish; + + # get currencies and closedto + $query = qq|SELECT curr, closedto, current_date + FROM defaults|; + + ($form->{currencies}, $form->{closedto}, $form->{datepaid}) = $dbh->selectrow_array($query); + + if ($form->{payment} eq 'payments') { + # get language codes + $query = qq|SELECT * + FROM language + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $form->{all_language} = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_language} }, $ref; + } + + $sth->finish; + + $form->all_departments($myconfig, $dbh, $form->{vc}); + } + + $dbh->disconnect; + +} + + +sub get_openvc { + + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $arap = ($form->{vc} eq 'customer') ? 'ar' : 'ap'; + my $query = qq|SELECT count(*) + FROM $form->{vc} ct, $arap a + WHERE a.$form->{vc}_id = ct.id + AND a.amount != a.paid|; + + my ($count) = $dbh->selectrow_array($query); + + my $sth; + my $ref; + my $i = 0; + + my $where = qq|WHERE a.$form->{vc}_id = ct.id + AND a.amount != a.paid|; + + if ($form->{$form->{vc}}) { + my $var = $form->like(lc $form->{$form->{vc}}); + $where .= " AND lower(name) LIKE '$var'"; + } + + # build selection list + $query = qq|SELECT DISTINCT ct.* + FROM $form->{vc} ct, $arap a + $where + ORDER BY name|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $i++; + push @{ $form->{name_list} }, $ref; + } + + $sth->finish; + + $form->all_departments($myconfig, $dbh, $form->{vc}); + + # get language codes + $query = qq|SELECT * + FROM language + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $form->{all_language} = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_language} }, $ref; + } + + $sth->finish; + + # get currency for first name + if (@{ $form->{name_list} }) { + $query = qq|SELECT curr + FROM $form->{vc} + WHERE id = $form->{name_list}->[0]->{id}|; + + ($form->{currency}) = $dbh->selectrow_array($query); + $form->{currency} ||= $form->{defaultcurrency}; + } + + $dbh->disconnect; + + $i; +} + + +sub get_openinvoices { + + my ($self, $myconfig, $form) = @_; + + my $null; + my $department_id; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $where = qq|WHERE a.$form->{vc}_id = $form->{"$form->{vc}_id"} + AND a.amount != a.paid|; + + $where .= qq| AND a.curr = '$form->{currency}'| if $form->{currency}; + + my $sortorder = "transdate, invnumber"; + + my ($buysell); + + if ($form->{vc} eq 'customer') { + $buysell = "buy"; + } else { + $buysell = "sell"; + } + + if ($form->{payment} eq 'payments') { + + $where = qq|WHERE a.amount != a.paid|; + $where .= qq| AND a.curr = '$form->{currency}'| if $form->{currency}; + + if ($form->{duedatefrom}) { + $where .= qq| AND a.duedate >= '$form->{duedatefrom}'|; + } + + if ($form->{duedateto}) { + $where .= qq| AND a.duedate <= '$form->{duedateto}'|; + } + + $sortorder = "name, transdate"; + } + + + ($null, $department_id) = split /--/, $form->{department}; + + if ($department_id) { + $where .= qq| AND a.department_id = $department_id|; + } + + my $query = qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid, + a.curr, c.name, a.$form->{vc}_id, c.language_code + FROM $form->{arap} a + JOIN $form->{vc} c ON (c.id = a.$form->{vc}_id) + $where + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|SELECT s.spoolfile + FROM status s + WHERE s.formname = '$form->{formname}' + AND s.trans_id = ?|; + + my $vth = $dbh->prepare($query); + + my $spoolfile; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + # if this is a foreign currency transaction get exchangerate + $ref->{exchangerate} = $form->get_exchangerate($dbh, $ref->{curr}, $ref->{transdate}, $buysell) if ($form->{currency} ne $form->{defaultcurrency}); + + $vth->execute($ref->{id}); + $ref->{queue} = ""; + + while (($spoolfile) = $vth->fetchrow_array) { + $ref->{queued} .= "$form->{formname} $spoolfile "; + } + + $vth->finish; + $ref->{queued} =~ s/ +$//g; + + push @{ $form->{PR} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + + +sub post_payment { + + my ($self, $myconfig, $form) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $sth; + + my ($paymentaccno) = split /--/, $form->{account}; + + # if currency ne defaultcurrency update exchangerate + if ($form->{currency} ne $form->{defaultcurrency}) { + + $form->{exchangerate} = $form->parse_amount($myconfig, $form->{exchangerate}); + + if ($form->{vc} eq 'customer') { + $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, $form->{exchangerate}, 0); + } else { + $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, 0, $form->{exchangerate}); + } + + } else { + $form->{exchangerate} = 1; + } + + my $query = qq|SELECT fxgain_accno_id, fxloss_accno_id + FROM defaults|; + + my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query); + + my ($buysell); + + if ($form->{vc} eq 'customer') { + $buysell = "buy"; + } else { + $buysell = "sell"; + } + + my $ml; + my $where; + + if ($form->{ARAP} eq 'AR') { + + $ml = 1; + $where = qq| (c.link = 'AR' OR c.link LIKE 'AR:%') |; + + } else { + + $ml = -1; + $where = qq| (c.link = 'AP' OR c.link LIKE '%:AP' OR c.link LIKE '%:AP:%') |; + + } + + my $paymentamount = $form->parse_amount($myconfig, $form->{amount}); + + # query to retrieve paid amount + $query = qq|SELECT paid + FROM $form->{arap} + WHERE id = ? + FOR UPDATE|; + + my $pth = $dbh->prepare($query) || $form->dberror($query); + + my %audittrail; + + # go through line by line + for my $i (1 .. $form->{rowcount}) { + + $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"}); + $form->{"due_$i"} = $form->parse_amount($myconfig, $form->{"due_$i"}); + + if ($form->{"checked_$i"} && $form->{"paid_$i"}) { + + $paymentamount -= $form->{"paid_$i"}; + + # get exchangerate for original + $query = qq|SELECT $buysell + FROM exchangerate e + JOIN $form->{arap} a ON (a.transdate = e.transdate) + WHERE e.curr = '$form->{currency}' + AND a.id = $form->{"id_$i"}|; + + my ($exchangerate) = $dbh->selectrow_array($query); + + $exchangerate = 1 unless $exchangerate; + + $query = qq|SELECT c.id + FROM chart c + JOIN acc_trans a ON (a.chart_id = c.id) + WHERE $where + AND a.trans_id = $form->{"id_$i"}|; + + my ($id) = $dbh->selectrow_array($query); + + $amount = $form->round_amount($form->{"paid_$i"} * $exchangerate, 2); + + # add AR/AP + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, amount) + VALUES ($form->{"id_$i"}, $id, '$form->{datepaid}', $amount * $ml)|; + + $dbh->do($query) || $form->dberror($query); + + # add payment + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, source, memo) + VALUES ($form->{"id_$i"}, (SELECT id + FROM chart + WHERE accno = '$paymentaccno'), + '$form->{datepaid}', $form->{"paid_$i"} * $ml * -1, | + .$dbh->quote($form->{source}).qq|, | + .$dbh->quote($form->{memo}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + + # add exchangerate difference if currency ne defaultcurrency + $amount = $form->round_amount($form->{"paid_$i"} * ($form->{exchangerate} - 1), 2); + + if ($amount) { + # exchangerate difference + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, cleared, fx_transaction, source) + VALUES ($form->{"id_$i"}, (SELECT id + FROM chart + WHERE accno = '$paymentaccno'), + '$form->{datepaid}', $amount * $ml * -1, '0', '1', | + .$dbh->quote($form->{source}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + + # gain/loss + $amount = ($form->round_amount($form->{"paid_$i"} * $exchangerate,2) - $form->round_amount($form->{"paid_$i"} * $form->{exchangerate},2)) * $ml * -1; + + if ($amount) { + + my $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, cleared, fx_transaction) + VALUES ($form->{"id_$i"}, $accno_id, + '$form->{datepaid}', $amount, '0', '1')|; + + $dbh->do($query) || $form->dberror($query); + } + } + + $form->{"paid_$i"} = $form->round_amount($form->{"paid_$i"} * $exchangerate, 2); + + $pth->execute($form->{"id_$i"}) || $form->dberror; + ($amount) = $pth->fetchrow_array; + $pth->finish; + + $amount += $form->{"paid_$i"}; + + # update AR/AP transaction + $query = qq|UPDATE $form->{arap} + SET paid = $amount, + datepaid = '$form->{datepaid}' + WHERE id = $form->{"id_$i"}|; + + $dbh->do($query) || $form->dberror($query); + + %audittrail = ( tablename => $form->{arap}, + reference => $form->{source}, + formname => $form->{formname}, + action => 'posted', + id => $form->{"id_$i"} ); + + $form->audittrail($dbh, "", \%audittrail); + + } + } + + + # record a AR/AP with a payment + if ($form->round_amount($paymentamount, 2)) { + $form->{invnumber} = ""; + OP::overpayment("", $myconfig, $form, $dbh, $paymentamount, $ml, 1); + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub post_payments { + + my ($self, $myconfig, $form) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $sth; + + my ($paymentaccno) = split /--/, $form->{account}; + + # if currency ne defaultcurrency update exchangerate + if ($form->{currency} ne $form->{defaultcurrency}) { + $form->{exchangerate} = $form->parse_amount($myconfig, $form->{exchangerate}); + + if ($form->{vc} eq 'customer') { + $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, $form->{exchangerate}, 0); + } else { + $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, 0, $form->{exchangerate}); + } + + } else { + $form->{exchangerate} = 1; + } + + my $query = qq|SELECT fxgain_accno_id, fxloss_accno_id + FROM defaults|; + + my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query); + + my ($buysell); + + if ($form->{vc} eq 'customer') { + $buysell = "buy"; + } else { + $buysell = "sell"; + } + + my $ml; + my $where; + + if ($form->{ARAP} eq 'AR') { + + $ml = 1; + $where = qq| (c.link = 'AR' OR c.link LIKE 'AR:%') |; + + } else { + + $ml = -1; + $where = qq| (c.link = 'AP' OR c.link LIKE '%:AP' OR c.link LIKE '%:AP:%') |; + + } + + # get AR/AP account + $query = qq|SELECT c.accno + FROM chart c + JOIN acc_trans ac ON (ac.chart_id = c.id) + WHERE trans_id = ? + AND $where|; + + my $ath = $dbh->prepare($query) || $form->dberror($query); + + # query to retrieve paid amount + $query = qq|SELECT paid + FROM $form->{arap} + WHERE id = ? + FOR UPDATE|; + + my $pth = $dbh->prepare($query) || $form->dberror($query); + + my %audittrail; + + my $overpayment = 0; + my $accno_id; + + # go through line by line + for my $i (1 .. $form->{rowcount}) { + + $ath->execute($form->{"id_$i"}); + ($form->{$form->{ARAP}}) = $ath->fetchrow_array; + $ath->finish; + + $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"}); + $form->{"due_$i"} = $form->parse_amount($myconfig, $form->{"due_$i"}); + + if ($form->{"$form->{vc}_id_$i"} ne $sameid) { + # record a AR/AP with a payment + if ($overpayment > 0 && $form->{$form->{ARAP}}) { + $form->{invnumber} = ""; + OP::overpayment("", $myconfig, $form, $dbh, $overpayment, $ml, 1); + } + + $overpayment = 0; + $form->{"$form->{vc}_id"} = $form->{"$form->{vc}_id_$i"}; + for (qw(source memo)) { $form->{$_} = $form->{"${_}_$i"} } + } + + if ($form->{"checked_$i"} && $form->{"paid_$i"}) { + + $overpayment += ($form->{"paid_$i"} - $form->{"due_$i"}); + + # get exchangerate for original + $query = qq|SELECT $buysell + FROM exchangerate e + JOIN $form->{arap} a ON (a.transdate = e.transdate) + WHERE e.curr = '$form->{currency}' + AND a.id = $form->{"id_$i"}|; + + my ($exchangerate) = $dbh->selectrow_array($query); + + $exchangerate ||= 1; + + $query = qq|SELECT c.id + FROM chart c + JOIN acc_trans a ON (a.chart_id = c.id) + WHERE $where + AND a.trans_id = $form->{"id_$i"}|; + + my ($id) = $dbh->selectrow_array($query); + + $paid = ($form->{"paid_$i"} > $form->{"due_$i"}) ? $form->{"due_$i"} : $form->{"paid_$i"}; + $amount = $form->round_amount($paid * $exchangerate, 2); + + # add AR/AP + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, amount) + VALUES ($form->{"id_$i"}, $id, '$form->{datepaid}', + $amount * $ml)|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM chart + WHERE accno = '$paymentaccno'|; + + ($accno_id) = $dbh->selectrow_array($query); + + # add payment + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, source, memo) + VALUES ($form->{"id_$i"}, $accno_id, '$form->{datepaid}', + $paid * $ml * -1, | + .$dbh->quote($form->{source}).qq|, | + .$dbh->quote($form->{memo}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + + # add exchangerate difference if currency ne defaultcurrency + $amount = $form->round_amount($paid * ($form->{exchangerate} - 1) * $ml * -1, 2); + + if ($amount) { + # exchangerate difference + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, source) + VALUES ($form->{"id_$i"}, $accno_id, '$form->{datepaid}', + $amount, | + .$dbh->quote($form->{source}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + + # gain/loss + $amount = ($form->round_amount($paid * $exchangerate,2) - $form->round_amount($paid * $form->{exchangerate},2)) * $ml * -1; + + if ($amount) { + $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, fx_transaction) + VALUES ($form->{"id_$i"}, $accno_id, + '$form->{datepaid}', $amount, '1')|; + + $dbh->do($query) || $form->dberror($query); + } + } + + $paid = $form->round_amount($paid * $exchangerate, 2); + + $pth->execute($form->{"id_$i"}) || $form->dberror; + ($amount) = $pth->fetchrow_array; + $pth->finish; + + $amount += $paid; + + # update AR/AP transaction + $query = qq|UPDATE $form->{arap} + SET paid = $amount, + datepaid = '$form->{datepaid}' + WHERE id = $form->{"id_$i"}|; + + $dbh->do($query) || $form->dberror($query); + + %audittrail = ( tablename => $form->{arap}, + reference => $form->{source}, + formname => $form->{formname}, + action => 'posted', + id => $form->{"id_$i"} ); + + $form->audittrail($dbh, "", \%audittrail); + + } + + $sameid = $form->{"$form->{vc}_id_$i"}; + + } + + # record a AR/AP with a payment + if ($overpayment > 0 && $form->{$form->{ARAP}}) { + $form->{invnumber} = ""; + OP::overpayment("", $myconfig, $form, $dbh, $overpayment, $ml, 1); + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +1; + diff --git a/LedgerSMB/CT.pm b/LedgerSMB/CT.pm new file mode 100755 index 00000000..a99c7292 --- /dev/null +++ b/LedgerSMB/CT.pm @@ -0,0 +1,1080 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# backend code for customers and vendors +# +#====================================================================== + +package CT; + + +sub create_links { + + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + my $query; + my $sth; + my $ref; + my $arap = ($form->{db} eq 'customer') ? "ar" : "ap"; + my $ARAP = uc $arap; + + if ($form->{id}) { + $query = qq|SELECT ct.*, b.description AS business, s.*, + e.name AS employee, g.pricegroup AS pricegroup, + l.description AS language, ct.curr + FROM $form->{db} ct + LEFT JOIN business b ON (ct.business_id = b.id) + LEFT JOIN shipto s ON (ct.id = s.trans_id) + LEFT JOIN employee e ON (ct.employee_id = e.id) + LEFT JOIN pricegroup g ON (g.id = ct.pricegroup_id) + LEFT JOIN language l ON (l.code = ct.language_code) + WHERE ct.id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # check if it is orphaned + $query = qq|SELECT a.id + FROM $arap a + JOIN $form->{db} ct ON (a.$form->{db}_id = ct.id) + WHERE ct.id = $form->{id} + + UNION + + SELECT a.id + FROM oe a + JOIN $form->{db} ct ON (a.$form->{db}_id = ct.id) + WHERE ct.id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + unless ($sth->fetchrow_array) { + $form->{status} = "orphaned"; + } + + $sth->finish; + + # get taxes for customer/vendor + $query = qq|SELECT c.accno + FROM chart c + JOIN $form->{db}tax t ON (t.chart_id = c.id) + WHERE t.$form->{db}_id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{tax}{$ref->{accno}}{taxable} = 1; + } + + $sth->finish; + + } else { + + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + + $query = qq|SELECT current_date FROM defaults|; + ($form->{startdate}) = $dbh->selectrow_array($query); + + } + + # get tax labels + $query = qq|SELECT DISTINCT c.accno, c.description + FROM chart c + JOIN tax t ON (t.chart_id = c.id) + WHERE c.link LIKE '%${ARAP}_tax%' + ORDER BY c.accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{taxaccounts} .= "$ref->{accno} "; + $form->{tax}{$ref->{accno}}{description} = $ref->{description}; + } + + $sth->finish; + chop $form->{taxaccounts}; + + + # get business types ## needs fixing, this is bad (SELECT * ...) with order by 2. Yuck + $query = qq|SELECT * + FROM business + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_business} }, $ref; + } + + $sth->finish; + + # employees/salespersons + $form->all_employees($myconfig, $dbh, undef, ($form->{vc} eq 'customer') ? 1 : 0); + + # get language ## needs fixing, this is bad (SELECT * ...) with order by 2. Yuck + $query = qq|SELECT * + FROM language + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_language} }, $ref; + } + + $sth->finish; + + # get pricegroups ## needs fixing, this is bad (SELECT * ...) with order by 2. Yuck + $query = qq|SELECT * + FROM pricegroup + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_pricegroup} }, $ref; + } + + $sth->finish; + + # get currencies + $query = qq|SELECT curr AS currencies + FROM defaults|; + + ($form->{currencies}) = $dbh->selectrow_array($query); + + $dbh->disconnect; + +} + + +sub save_customer { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + my $query; + my $sth; + my $null; + + # remove double spaces + $form->{name} =~ s/ / /g; + # remove double minus and minus at the end + $form->{name} =~ s/--+/-/g; + $form->{name} =~ s/-+$//; + + # assign value discount, terms, creditlimit + $form->{discount} = $form->parse_amount($myconfig, $form->{discount}); + $form->{discount} /= 100; + $form->{terms} *= 1; + $form->{taxincluded} *= 1; + $form->{creditlimit} = $form->parse_amount($myconfig, $form->{creditlimit}); + + + if ($form->{id}) { + $query = qq|DELETE FROM customertax + WHERE customer_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM shipto + WHERE trans_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM customer + WHERE id = $form->{id}|; + + if (! $dbh->selectrow_array($query)) { + $query = qq|INSERT INTO customer (id) + VALUES ($form->{id})|; + + $dbh->do($query) || $form->dberror($query); + } + + # retrieve enddate + if ($form->{type} && $form->{enddate}) { + my $now; + $query = qq|SELECT enddate, current_date AS now FROM customer|; + ($form->{enddate}, $now) = $dbh->selectrow_array($query); + $form->{enddate} = $now if $form->{enddate} lt $now; + } + + } else { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO customer (name) + VALUES ('$uid')|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM customer + WHERE name = '$uid'|; + + ($form->{id}) = $dbh->selectrow_array($query); + + } + + my $employee_id; + ($null, $employee_id) = split /--/, $form->{employee}; + $employee_id *= 1; + + my $pricegroup_id; + ($null, $pricegroup_id) = split /--/, $form->{pricegroup}; + $pricegroup_id *= 1; + + my $business_id; + ($null, $business_id) = split /--/, $form->{business}; + $business_id *= 1; + + my $language_code; + ($null, $language_code) = split /--/, $form->{language}; + + $form->{customernumber} = $form->update_defaults($myconfig, "customernumber", $dbh) if ! $form->{customernumber}; + + $query = qq|UPDATE customer + SET customernumber = |.$dbh->quote($form->{customernumber}).qq|, + name = |.$dbh->quote($form->{name}).qq|, + address1 = |.$dbh->quote($form->{address1}).qq|, + address2 = |.$dbh->quote($form->{address2}).qq|, + city = |.$dbh->quote($form->{city}).qq|, + state = |.$dbh->quote($form->{state}).qq|, + zipcode = |.$dbh->quote($form->{zipcode}).qq|, + country = |.$dbh->quote($form->{country}).qq|, + contact = |.$dbh->quote($form->{contact}).qq|, + phone = '$form->{phone}', + fax = '$form->{fax}', + email = '$form->{email}', + cc = '$form->{cc}', + bcc = '$form->{bcc}', + notes = |.$dbh->quote($form->{notes}).qq|, + discount = $form->{discount}, + creditlimit = $form->{creditlimit}, + terms = $form->{terms}, + taxincluded = '$form->{taxincluded}', + business_id = $business_id, + taxnumber = |.$dbh->quote($form->{taxnumber}).qq|, + sic_code = '$form->{sic_code}', + iban = '$form->{iban}', + bic = '$form->{bic}', + employee_id = $employee_id, + pricegroup_id = $pricegroup_id, + language_code = '$language_code', + curr = '$form->{curr}', + startdate = |.$form->dbquote($form->{startdate}, SQL_DATE).qq|, + enddate = |.$form->dbquote($form->{enddate}, SQL_DATE).qq| + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + # save taxes + foreach $item (split / /, $form->{taxaccounts}) { + + if ($form->{"tax_$item"}) { + $query = qq|INSERT INTO customertax (customer_id, chart_id) + VALUES ($form->{id}, (SELECT id + FROM chart + WHERE accno = '$item'))|; + + $dbh->do($query) || $form->dberror($query); + } + } + + # add shipto + $form->add_shipto($dbh, $form->{id}); + + $dbh->commit; + $dbh->disconnect; +} + + +sub save_vendor { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + my $null; + + # remove double spaces + $form->{name} =~ s/ / /g; + # remove double minus and minus at the end + $form->{name} =~ s/--+/-/g; + $form->{name} =~ s/-+$//; + + $form->{discount} = $form->parse_amount($myconfig, $form->{discount}); + $form->{discount} /= 100; + $form->{terms} *= 1; + $form->{taxincluded} *= 1; + $form->{creditlimit} = $form->parse_amount($myconfig, $form->{creditlimit}); + + + if ($form->{id}) { + $query = qq|DELETE FROM vendortax + WHERE vendor_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM shipto + WHERE trans_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM vendor + WHERE id = $form->{id}|; + + if (! $dbh->selectrow_array($query)) { + $query = qq|INSERT INTO vendor (id) + VALUES ($form->{id})|; + + $dbh->do($query) || $form->dberror($query); + } + + # retrieve enddate + if ($form->{type} && $form->{enddate}) { + my $now; + $query = qq|SELECT enddate, current_date AS now FROM vendor|; + ($form->{enddate}, $now) = $dbh->selectrow_array($query); + $form->{enddate} = $now if $form->{enddate} lt $now; + } + + } else { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO vendor (name) + VALUES ('$uid')|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM vendor + WHERE name = '$uid'|; + + ($form->{id}) = $dbh->selectrow_array($query); + + } + + my $employee_id; + ($null, $employee_id) = split /--/, $form->{employee}; + $employee_id *= 1; + + my $pricegroup_id; + ($null, $pricegroup_id) = split /--/, $form->{pricegroup}; + $pricegroup_id *= 1; + + my $business_id; + ($null, $business_id) = split /--/, $form->{business}; + $business_id *= 1; + + my $language_code; + ($null, $language_code) = split /--/, $form->{language}; + + $form->{vendornumber} = $form->update_defaults($myconfig, "vendornumber", $dbh) if ! $form->{vendornumber}; + + $query = qq|UPDATE vendor + SET vendornumber = |.$dbh->quote($form->{vendornumber}).qq|, + name = |.$dbh->quote($form->{name}).qq|, + address1 = |.$dbh->quote($form->{address1}).qq|, + address2 = |.$dbh->quote($form->{address2}).qq|, + city = |.$dbh->quote($form->{city}).qq|, + state = |.$dbh->quote($form->{state}).qq|, + zipcode = |.$dbh->quote($form->{zipcode}).qq|, + country = |.$dbh->quote($form->{country}).qq|, + contact = |.$dbh->quote($form->{contact}).qq|, + phone = '$form->{phone}', + fax = '$form->{fax}', + email = '$form->{email}', + cc = '$form->{cc}', + bcc = '$form->{bcc}', + notes = |.$dbh->quote($form->{notes}).qq|, + terms = $form->{terms}, + discount = $form->{discount}, + creditlimit = $form->{creditlimit}, + taxincluded = '$form->{taxincluded}', + gifi_accno = '$form->{gifi_accno}', + business_id = $business_id, + taxnumber = |.$dbh->quote($form->{taxnumber}).qq|, + sic_code = '$form->{sic_code}', + iban = '$form->{iban}', + bic = '$form->{bic}', + employee_id = $employee_id, + language_code = '$language_code', + pricegroup_id = $pricegroup_id, + curr = '$form->{curr}', + startdate = |.$form->dbquote($form->{startdate}, SQL_DATE).qq|, + enddate = |.$form->dbquote($form->{enddate}, SQL_DATE).qq| + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + # save taxes + foreach $item (split / /, $form->{taxaccounts}) { + if ($form->{"tax_$item"}) { + $query = qq|INSERT INTO vendortax (vendor_id, chart_id) + VALUES ($form->{id}, (SELECT id + FROM chart + WHERE accno = '$item'))|; + + $dbh->do($query) || $form->dberror($query); + } + } + + # add shipto + $form->add_shipto($dbh, $form->{id}); + + $dbh->commit; + $dbh->disconnect; + +} + + + +sub delete { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # delete customer/vendor + my $query = qq|DELETE FROM $form->{db} + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + +sub search { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $where = "1 = 1"; + $form->{sort} = ($form->{sort}) ? $form->{sort} : "name"; + my @a = qw(name); + my $sortorder = $form->sort_order(\@a); + + my $var; + my $item; + + @a = ("$form->{db}number"); + push @a, qw(name contact city state zipcode country notes phone email); + + if ($form->{employee}) { + $var = $form->like(lc $form->{employee}); + $where .= " AND lower(e.name) LIKE '$var'"; + } + + foreach $item (@a) { + + if ($form->{$item} ne "") { + $var = $form->like(lc $form->{$item}); + $where .= " AND lower(ct.$item) LIKE '$var'"; + } + } + + if ($form->{address} ne "") { + $var = $form->like(lc $form->{address}); + $where .= " AND (lower(ct.address1) LIKE '$var' OR lower(ct.address2) LIKE '$var')"; + } + + if ($form->{startdatefrom}) { + $where .= " AND ct.startdate >= '$form->{startdatefrom}'"; + } + + if ($form->{startdateto}) { + $where .= " AND ct.startdate <= '$form->{startdateto}'"; + } + + if ($form->{status} eq 'active') { + $where .= " AND ct.enddate IS NULL"; + } + + if ($form->{status} eq 'inactive') { + $where .= " AND ct.enddate <= current_date"; + } + + if ($form->{status} eq 'orphaned') { + $where .= qq| AND ct.id NOT IN (SELECT o.$form->{db}_id + FROM oe o, $form->{db} vc + WHERE vc.id = o.$form->{db}_id)|; + + if ($form->{db} eq 'customer') { + $where .= qq| AND ct.id NOT IN (SELECT a.customer_id + FROM ar a, customer vc + WHERE vc.id = a.customer_id)|; + } + + if ($form->{db} eq 'vendor') { + $where .= qq| AND ct.id NOT IN (SELECT a.vendor_id + FROM ap a, vendor vc + WHERE vc.id = a.vendor_id)|; + } + + $form->{l_invnumber} = $form->{l_ordnumber} = $form->{l_quonumber} = ""; + } + + + my $query = qq|SELECT ct.*, b.description AS business, + e.name AS employee, g.pricegroup, l.description AS language, + m.name AS manager + FROM $form->{db} ct + LEFT JOIN business b ON (ct.business_id = b.id) + LEFT JOIN employee e ON (ct.employee_id = e.id) + LEFT JOIN employee m ON (m.id = e.managerid) + LEFT JOIN pricegroup g ON (ct.pricegroup_id = g.id) + LEFT JOIN language l ON (l.code = ct.language_code) + WHERE $where|; + + # redo for invoices, orders and quotations + if ($form->{l_transnumber} || $form->{l_invnumber} || $form->{l_ordnumber} || $form->{l_quonumber}) { + + my ($ar, $union, $module); + $query = ""; + my $transwhere; + my $openarap = ""; + my $openoe = ""; + + if ($form->{open} || $form->{closed}) { + unless ($form->{open} && $form->{closed}) { + $openarap = " AND a.amount != a.paid" if $form->{open}; + $openarap = " AND a.amount = a.paid" if $form->{closed}; + $openoe = " AND o.closed = '0'" if $form->{open}; + $openoe = " AND o.closed = '1'" if $form->{closed}; + } + } + + if ($form->{l_transnumber}) { + + $ar = ($form->{db} eq 'customer') ? 'ar' : 'ap'; + $module = $ar; + + $transwhere = ""; + $transwhere .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $transwhere .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + + $query = qq|SELECT ct.*, b.description AS business, + a.invnumber, a.ordnumber, a.quonumber, a.id AS invid, + '$ar' AS module, 'invoice' AS formtype, + (a.amount = a.paid) AS closed, a.amount, a.netamount, + e.name AS employee, m.name AS manager + FROM $form->{db} ct + JOIN $ar a ON (a.$form->{db}_id = ct.id) + LEFT JOIN business b ON (ct.business_id = b.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + LEFT JOIN employee m ON (m.id = e.managerid) + WHERE $where + AND a.invoice = '0' + $transwhere + $openarap |; + + $union = qq| UNION |; + + } + + if ($form->{l_invnumber}) { + $ar = ($form->{db} eq 'customer') ? 'ar' : 'ap'; + $module = ($ar eq 'ar') ? 'is' : 'ir'; + + $transwhere = ""; + $transwhere .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $transwhere .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + $query .= qq|$union + SELECT ct.*, b.description AS business, + a.invnumber, a.ordnumber, a.quonumber, a.id AS invid, + '$module' AS module, 'invoice' AS formtype, + (a.amount = a.paid) AS closed, a.amount, a.netamount, + e.name AS employee, m.name AS manager + FROM $form->{db} ct + JOIN $ar a ON (a.$form->{db}_id = ct.id) + LEFT JOIN business b ON (ct.business_id = b.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + LEFT JOIN employee m ON (m.id = e.managerid) + WHERE $where + AND a.invoice = '1' + $transwhere + $openarap |; + + $union = qq| UNION|; + + } + + if ($form->{l_ordnumber}) { + + $transwhere = ""; + $transwhere .= " AND o.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $transwhere .= " AND o.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + $query .= qq|$union + SELECT ct.*, b.description AS business, + ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, + 'oe' AS module, 'order' AS formtype, + o.closed, o.amount, o.netamount, + e.name AS employee, m.name AS manager + FROM $form->{db} ct + JOIN oe o ON (o.$form->{db}_id = ct.id) + LEFT JOIN business b ON (ct.business_id = b.id) + LEFT JOIN employee e ON (o.employee_id = e.id) + LEFT JOIN employee m ON (m.id = e.managerid) + WHERE $where + AND o.quotation = '0' + $transwhere + $openoe |; + + $union = qq| UNION|; + + } + + if ($form->{l_quonumber}) { + + $transwhere = ""; + $transwhere .= " AND o.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $transwhere .= " AND o.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + $query .= qq|$union + SELECT ct.*, b.description AS business, + ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, + 'oe' AS module, 'quotation' AS formtype, + o.closed, o.amount, o.netamount, + e.name AS employee, m.name AS manager + FROM $form->{db} ct + JOIN oe o ON (o.$form->{db}_id = ct.id) + LEFT JOIN business b ON (ct.business_id = b.id) + LEFT JOIN employee e ON (o.employee_id = e.id) + LEFT JOIN employee m ON (m.id = e.managerid) + WHERE $where + AND o.quotation = '1' + $transwhere + $openoe |; + + } + + $sortorder .= ", invid"; + } + + $query .= qq| ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # accounts + $query = qq|SELECT c.accno + FROM chart c + JOIN $form->{db}tax t ON (t.chart_id = c.id) + WHERE t.$form->{db}_id = ?|; + + my $tth = $dbh->prepare($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $tth->execute($ref->{id}); + + while (($item) = $tth->fetchrow_array) { + $ref->{taxaccount} .= "$item "; + } + + $tth->finish; + chop $ref->{taxaccount}; + + $ref->{address} = ""; + + for (qw(address1 address2 city state zipcode country)) { $ref->{address} .= "$ref->{$_} " } + push @{ $form->{CT} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub get_history { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $where = "1 = 1"; + $form->{sort} = "partnumber" unless $form->{sort}; + my $sortorder = $form->{sort}; + my %ordinal = (); + my $var; + my $table; + + # setup ASC or DESC + $form->sort_order(); + + if ($form->{"$form->{db}number"} ne "") { + $var = $form->like(lc $form->{"$form->{db}number"}); + $where .= " AND lower(ct.$form->{db}number) LIKE '$var'"; + } + + if ($form->{address} ne "") { + $var = $form->like(lc $form->{address}); + $where .= " AND lower(ct.address1) LIKE '$var'"; + } + + for (qw(name contact email phone notes city state zipcode country)) { + + if ($form->{$_} ne "") { + $var = $form->like(lc $form->{$_}); + $where .= " AND lower(ct.$_) LIKE '$var'"; + } + } + + if ($form->{employee} ne "") { + $var = $form->like(lc $form->{employee}); + $where .= " AND lower(e.name) LIKE '$var'"; + } + + $where .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $where .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + if ($form->{open} || $form->{closed}) { + + unless ($form->{open} && $form->{closed}) { + + if ($form->{type} eq 'invoice') { + $where .= " AND a.amount != a.paid" if $form->{open}; + $where .= " AND a.amount = a.paid" if $form->{closed}; + } else { + $where .= " AND a.closed = '0'" if $form->{open}; + $where .= " AND a.closed = '1'" if $form->{closed}; + } + } + } + + my $invnumber = 'invnumber'; + my $deldate = 'deliverydate'; + my $buysell; + my $sellprice = "sellprice"; + + if ($form->{db} eq 'customer') { + $buysell = "buy"; + + if ($form->{type} eq 'invoice') { + $where .= qq| AND a.invoice = '1' AND i.assemblyitem = '0'|; + $table = 'ar'; + $sellprice = "fxsellprice"; + } else { + $table = 'oe'; + + if ($form->{type} eq 'order') { + $invnumber = 'ordnumber'; + $where .= qq| AND a.quotation = '0'|; + } else { + $invnumber = 'quonumber'; + $where .= qq| AND a.quotation = '1'|; + } + + $deldate = 'reqdate'; + } + } + + if ($form->{db} eq 'vendor') { + + $buysell = "sell"; + + if ($form->{type} eq 'invoice') { + + $where .= qq| AND a.invoice = '1' AND i.assemblyitem = '0'|; + $table = 'ap'; + $sellprice = "fxsellprice"; + + } else { + + $table = 'oe'; + + if ($form->{type} eq 'order') { + $invnumber = 'ordnumber'; + $where .= qq| AND a.quotation = '0'|; + } else { + $invnumber = 'quonumber'; + $where .= qq| AND a.quotation = '1'|; + } + + $deldate = 'reqdate'; + } + } + + my $invjoin = qq| JOIN invoice i ON (i.trans_id = a.id)|; + + if ($form->{type} eq 'order') { + $invjoin = qq| JOIN orderitems i ON (i.trans_id = a.id)|; + } + + if ($form->{type} eq 'quotation') { + $invjoin = qq| JOIN orderitems i ON (i.trans_id = a.id)|; + $where .= qq| AND a.quotation = '1'|; + } + + + %ordinal = ( partnumber => 9, + description => 12, + "$deldate" => 16, + serialnumber => 17, + projectnumber => 18 ); + + $sortorder = "2 $form->{direction}, 1, 11, $ordinal{$sortorder} $form->{direction}"; + + $query = qq|SELECT ct.id AS ctid, ct.name, ct.address1, + ct.address2, ct.city, ct.state, + p.id AS pid, p.partnumber, a.id AS invid, + a.$invnumber, a.curr, i.description, + i.qty, i.$sellprice AS sellprice, i.discount, + i.$deldate, i.serialnumber, pr.projectnumber, + e.name AS employee, ct.zipcode, ct.country, i.unit, + (SELECT $buysell + FROM exchangerate ex + WHERE a.curr = ex.curr + AND a.transdate = ex.transdate) AS exchangerate + FROM $form->{db} ct + JOIN $table a ON (a.$form->{db}_id = ct.id) + $invjoin + JOIN parts p ON (p.id = i.parts_id) + LEFT JOIN project pr ON (pr.id = i.project_id) + LEFT JOIN employee e ON (e.id = a.employee_id) + WHERE $where + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{address} = ""; + $ref->{exchangerate} ||= 1; + for (qw(address1 address2 city state zipcode country)) { $ref->{address} .= "$ref->{$_} " } + $ref->{id} = $ref->{ctid}; + push @{ $form->{CT} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub pricelist { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + + if ($form->{db} eq 'customer') { + $query = qq|SELECT p.id, p.partnumber, p.description, + p.sellprice, pg.partsgroup, p.partsgroup_id, + m.pricebreak, m.sellprice, + m.validfrom, m.validto, m.curr + FROM partscustomer m + JOIN parts p ON (p.id = m.parts_id) + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + WHERE m.customer_id = $form->{id} + ORDER BY partnumber|; + } + + if ($form->{db} eq 'vendor') { + $query = qq|SELECT p.id, p.partnumber AS sku, p.description, + pg.partsgroup, p.partsgroup_id, + m.partnumber, m.leadtime, m.lastcost, m.curr + FROM partsvendor m + JOIN parts p ON (p.id = m.parts_id) + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + WHERE m.vendor_id = $form->{id} + ORDER BY p.partnumber|; + } + + my $sth; + my $ref; + + if ($form->{id}) { + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_partspricelist} }, $ref; + } + + $sth->finish; + } + + $query = qq|SELECT curr FROM defaults|; + ($form->{currencies}) = $dbh->selectrow_array($query); + + $query = qq|SELECT id, partsgroup + FROM partsgroup + ORDER BY partsgroup|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $form->{all_partsgroup} = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_partsgroup} }, $ref; + } + + $sth->finish; + + $dbh->disconnect; + +} + + +sub save_pricelist { + + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query = qq|DELETE FROM parts$form->{db} + WHERE $form->{db}_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + foreach $i (1 .. $form->{rowcount}) { + + if ($form->{"id_$i"}) { + + if ($form->{db} eq 'customer') { + + for (qw(pricebreak sellprice)) { + $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) + } + + $query = qq|INSERT INTO parts$form->{db} (parts_id, customer_id, + pricebreak, sellprice, + validfrom, validto, curr) + VALUES ($form->{"id_$i"}, $form->{id}, + $form->{"pricebreak_$i"}, $form->{"sellprice_$i"},| + .$form->dbquote($form->{"validfrom_$i"}, SQL_DATE) .qq|,| + .$form->dbquote($form->{"validto_$i"}, SQL_DATE) .qq|, + '$form->{"curr_$i"}')|; + } else { + + for (qw(leadtime lastcost)) { + $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) + } + + $query = qq|INSERT INTO parts$form->{db} (parts_id, vendor_id, + partnumber, lastcost, + leadtime, curr) + VALUES ($form->{"id_$i"}, $form->{id}, + '$form->{"partnumber_$i"}', $form->{"lastcost_$i"}, + $form->{"leadtime_$i"}, '$form->{"curr_$i"}')|; + + } + $dbh->do($query) || $form->dberror($query); + } + + } + + $_ = $dbh->commit; + $dbh->disconnect; + +} + + + +sub retrieve_item { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $i = $form->{rowcount}; + my $var; + my $null; + + my $where = "WHERE p.obsolete = '0'"; + + if ($form->{db} eq 'vendor') { + # parts, services, labor + $where .= " AND p.assembly = '0'"; + } + + if ($form->{db} eq 'customer') { + # parts, assemblies, services + $where .= " AND p.income_accno_id > 0"; + } + + if ($form->{"partnumber_$i"} ne "") { + $var = $form->like(lc $form->{"partnumber_$i"}); + $where .= " AND lower(p.partnumber) LIKE '$var'"; + } + + if ($form->{"description_$i"} ne "") { + $var = $form->like(lc $form->{"description_$i"}); + $where .= " AND lower(p.description) LIKE '$var'"; + } + + if ($form->{"partsgroup_$i"} ne "") { + ($null, $var) = split /--/, $form->{"partsgroup_$i"}; + $var *= 1; + $where .= qq| AND p.partsgroup_id = $var|; + } + + + my $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, + p.lastcost, p.unit, pg.partsgroup, p.partsgroup_id + FROM parts p + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + $where + ORDER BY partnumber|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + my $ref; + $form->{item_list} = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{item_list} }, $ref; + } + + $sth->finish; + $dbh->disconnect; +} + + +1; + diff --git a/LedgerSMB/Form.pm b/LedgerSMB/Form.pm new file mode 100755 index 00000000..0fb59a1a --- /dev/null +++ b/LedgerSMB/Form.pm @@ -0,0 +1,2942 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# main package +# +#====================================================================== + +package Form; + + +sub new { + + my $type = shift; + + my $self = {}; + + read(STDIN, $_, $ENV{CONTENT_LENGTH}); + + if ($ENV{QUERY_STRING}) { + $_ = $ENV{QUERY_STRING}; + } + + if ($ARGV[0]) { + $_ = $ARGV[0]; + } + + %$self = split /[&=]/; + for (keys %$self) { $self->{$_} = unescape("", $self->{$_}) } + + if (substr($self->{action}, 0, 1) !~ /( |\.)/) { + $self->{action} = lc $self->{action}; + $self->{action} =~ s/( |-|,|\#|\/|\.$)/_/g; + } + + $self->{menubar} = 1 if $self->{path} =~ /lynx/i; + + $self->{version} = "2.6.17"; + $self->{dbversion} = "2.6.12"; + + bless $self, $type; + +} + + +sub debug { + + my ($self, $file) = @_; + + if ($file) { + open(FH, "> $file") or die $!; + for (sort keys %$self) { print FH "$_ = $self->{$_}\n" } + close(FH); + } else { + print "\n"; + for (sort keys %$self) { print "$_ = $self->{$_}\n" } + } + +} + + +sub escape { + my ($self, $str, $beenthere) = @_; + + # for Apache 2 we escape strings twice + if (($ENV{SERVER_SIGNATURE} =~ /Apache\/2\.(\d+)\.(\d+)/) && !$beenthere) { + $str = $self->escape($str, 1) if $1 == 0 && $2 < 44; + } + + $str =~ s/([^a-zA-Z0-9_.-])/sprintf("%%%02x", ord($1))/ge; + $str; + +} + + +sub unescape { + my ($self, $str) = @_; + + $str =~ tr/+/ /; + $str =~ s/\\$//; + + $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg; + $str =~ s/\r?\n/\n/g; + + $str; + +} + + +sub quote { + my ($self, $str) = @_; + + if ($str && ! ref($str)) { + $str =~ s/"/"/g; + } + + $str; + +} + + +sub unquote { + my ($self, $str) = @_; + + if ($str && ! ref($str)) { + $str =~ s/"/"/g; + } + + $str; + +} + + +sub hide_form { + my $self = shift; + + if (@_) { + + for (@_) { + print qq|<input type="hidden" name="$_" value="|.$self->quote($self->{$_}).qq|" />\n| + } + + } else { + delete $self->{header}; + + for (sort keys %$self) { + print qq|<input type="hidden" name="$_" value="|.$self->quote($self->{$_}).qq|" />\n| + } + } +} + + +sub error { + + my ($self, $msg) = @_; + + if ($ENV{HTTP_USER_AGENT}) { + + $self->{msg} = $msg; + $self->{format} = "html"; + $self->format_string(msg); + + delete $self->{pre}; + + if (!$self->{header}) { + $self->header; + } + + print qq|<body><h2 class="error:>Error!</h2> <p><b>$self->{msg}</b></body>|; + + exit; + + } else { + + if ($self->{error_function}) { + &{ $self->{error_function} }($msg); + } else { + die "Error: $msg\n"; + } + } +} + + +sub info { + my ($self, $msg) = @_; + + if ($ENV{HTTP_USER_AGENT}) { + $msg =~ s/\n/<br>/g; + + delete $self->{pre}; + + if (!$self->{header}) { + $self->header; + print qq| <body>|; + $self->{header} = 1; + } + + print "<b>$msg</b>"; + + } else { + + if ($self->{info_function}) { + &{ $self->{info_function} }($msg); + } else { + print "$msg\n"; + } + } +} + + +sub numtextrows { + + my ($self, $str, $cols, $maxrows) = @_; + + my $rows = 0; + + for (split /\n/, $str) { + $rows += int (((length) - 2)/$cols) + 1 + } + + $maxrows = $rows unless defined $maxrows; + + return ($rows > $maxrows) ? $maxrows : $rows; + +} + + +sub dberror { + my ($self, $msg) = @_; + $self->error("$msg\n".$DBI::errstr); +} + + +sub isblank { + my ($self, $name, $msg) = @_; + $self->error($msg) if $self->{$name} =~ /^\s*$/; +} + + +sub header { + + my ($self, $init) = @_; + + return if $self->{header}; + + my ($stylesheet, $favicon, $charset); + + if ($ENV{HTTP_USER_AGENT}) { + + if ($self->{stylesheet} && (-f "css/$self->{stylesheet}")) { + $stylesheet = qq|<link rel="stylesheet" href="css/$self->{stylesheet}" type="text/css" title="LedgerSMB stylesheet" />\n|; + } + + if ($self->{favicon} && (-f "$self->{favicon}")) { + $favicon = qq|<link rel="icon" href="$self->{favicon}" type="image/x-icon" /> + <link rel="shortcut icon" href="$self->{favicon}" type="image/x-icon" />\n|; + } + + if ($self->{charset}) { + $charset = qq|<meta http-equiv="content-type" content="text/html; charset=$self->{charset}" />\n|; + } + + $self->{titlebar} = ($self->{title}) ? "$self->{title} - $self->{titlebar}" : $self->{titlebar}; + + $self->set_cookie($init); + + print qq|Content-Type: text/html\n\n +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title>$self->{titlebar}</title> + <meta http-equiv="Pragma" content="no-cache" /> + <meta http-equiv="Expires" content="-1" /> + $favicon + $stylesheet + $charset + <meta name="robots" content="noindex,nofollow" /> +</head> + + $self->{pre} \n|; + } + + $self->{header} = 1; +} + + +sub set_cookie { + + my ($self, $init) = @_; + + $self->{timeout} = ($self->{timeout} > 0) ? $self->{timeout} : 3600; + my $t = ($self->{endsession}) ? time : time + $self->{timeout}; + + if ($ENV{HTTP_USER_AGENT}) { + + my @d = split / +/, scalar gmtime($t); + my $today = "$d[0], $d[2]-$d[1]-$d[4] $d[3] GMT"; + + if ($init) { + $self->{sessionid} = time; + } + + print qq|Set-Cookie: LedgerSMB-$self->{login}=$self->{sessionid}; expires=$today; path=/;\n| if $self->{login}; + } +} + + +sub redirect { + + my ($self, $msg) = @_; + + if ($self->{callback}) { + + my ($script, $argv) = split(/\?/, $self->{callback}); + exec ("perl", $script, $argv); + + } else { + + $self->info($msg); + } +} + + +sub sort_columns { + + my ($self, @columns) = @_; + + if ($self->{sort}) { + if (@columns) { + @columns = grep !/^$self->{sort}$/, @columns; + splice @columns, 0, 0, $self->{sort}; + } + } + + @columns; +} + + +sub sort_order { + + my ($self, $columns, $ordinal) = @_; + + # setup direction + if ($self->{direction}) { + + if ($self->{sort} eq $self->{oldsort}) { + + if ($self->{direction} eq 'ASC') { + $self->{direction} = "DESC"; + } else { + $self->{direction} = "ASC"; + } + } + + } else { + + $self->{direction} = "ASC"; + } + + $self->{oldsort} = $self->{sort}; + + my @a = $self->sort_columns(@{$columns}); + + if (%$ordinal) { + $a[0] = ($ordinal->{$a[$_]}) ? "$ordinal->{$a[0]} $self->{direction}" : "$a[0] $self->{direction}"; + + for (1 .. $#a) { + $a[$_] = $ordinal->{$a[$_]} if $ordinal->{$a[$_]} + } + + } else { + $a[0] .= " $self->{direction}"; + } + + $sortorder = join ',', @a; + $sortorder; +} + + +sub format_amount { + + my ($self, $myconfig, $amount, $places, $dash) = @_; + + if ($places =~ /\d+/) { + #$places = 4 if $places == 2; + $amount = $self->round_amount($amount, $places); + } + + # is the amount negative + my $negative = ($amount < 0); + + if ($amount) { + + if ($myconfig->{numberformat}) { + + my ($whole, $dec) = split /\./, "$amount"; + $whole =~ s/-//; + $amount = join '', reverse split //, $whole; + + if ($places) { + $dec .= "0" x $places; + $dec = substr($dec, 0, $places); + } + + if ($myconfig->{numberformat} eq '1,000.00') { + $amount =~ s/\d{3,}?/$&,/g; + $amount =~ s/,$//; + $amount = join '', reverse split //, $amount; + $amount .= "\.$dec" if ($dec ne ""); + } + + if ($myconfig->{numberformat} eq "1'000.00") { + $amount =~ s/\d{3,}?/$&'/g; + $amount =~ s/'$//; + $amount = join '', reverse split //, $amount; + $amount .= "\.$dec" if ($dec ne ""); + } + + if ($myconfig->{numberformat} eq '1.000,00') { + $amount =~ s/\d{3,}?/$&./g; + $amount =~ s/\.$//; + $amount = join '', reverse split //, $amount; + $amount .= ",$dec" if ($dec ne ""); + } + + if ($myconfig->{numberformat} eq '1000,00') { + $amount = "$whole"; + $amount .= ",$dec" if ($dec ne ""); + } + + if ($myconfig->{numberformat} eq '1000.00') { + $amount = "$whole"; + $amount .= ".$dec" if ($dec ne ""); + } + + if ($dash =~ /-/) { + $amount = ($negative) ? "($amount)" : "$amount"; + } elsif ($dash =~ /DRCR/) { + $amount = ($negative) ? "$amount DR" : "$amount CR"; + } else { + $amount = ($negative) ? "-$amount" : "$amount"; + } + } + + } else { + + if ($dash eq "0" && $places) { + + if ($myconfig->{numberformat} eq '1.000,00') { + $amount = "0".","."0" x $places; + } else { + $amount = "0"."."."0" x $places; + } + + } else { + $amount = ($dash ne "") ? "$dash" : ""; + } + } + + $amount; +} + + +sub parse_amount { + + my ($self, $myconfig, $amount) = @_; + + if (($myconfig->{numberformat} eq '1.000,00') || + ($myconfig->{numberformat} eq '1000,00')) { + + $amount =~ s/\.//g; + $amount =~ s/,/\./; + } + + if ($myconfig->{numberformat} eq "1'000.00") { + $amount =~ s/'//g; + } + + $amount =~ s/,//g; + return ($amount * 1); +} + + +sub round_amount { + + my ($self, $amount, $places) = @_; + + # $places = 4 if $places == 2; + my ($null, $dec) = split /\./, $amount; + $dec = length $dec; + $dec = ($dec > $places) ? $dec : $places; + my $adj = ($amount < 0) ? (1/10**($dec+2)) * -1 : (1/10**($dec+2)); + + if (($places * 1) >= 0) { + $amount = sprintf("%.${places}f", $amount + $adj) * 1; + } else { + $places *= -1; + $amount = sprintf("%.0f", $amount); + $amount = sprintf("%.f", $amount / (10 ** $places)) * (10 ** $places); + } + + $amount; +} + + +sub parse_template { + + my ($self, $myconfig, $userspath) = @_; + + my ($chars_per_line, $lines_on_first_page, $lines_on_second_page) = (0, 0, 0); + my ($current_page, $current_line) = (1, 1); + my $pagebreak = ""; + my $sum = 0; + + my $subdir = ""; + my $err = ""; + + my %include = (); + my $ok; + + if ($self->{language_code}) { + + if (-f "$self->{templates}/$self->{language_code}/$self->{IN}") { + open(IN, "$self->{templates}/$self->{language_code}/$self->{IN}") or $self->error("$self->{IN} : $!"); + } else { + open(IN, "$self->{templates}/$self->{IN}") or $self->error("$self->{IN} : $!"); + } + + } else { + open(IN, "$self->{templates}/$self->{IN}") or $self->error("$self->{IN} : $!"); + } + + @_ = <IN>; + close(IN); + + $self->{copies} = 1 if (($self->{copies} *= 1) <= 0); + + # OUT is used for the media, screen, printer, email + # for postscript we store a copy in a temporary file + my $fileid = time; + my $tmpfile = $self->{IN}; + $tmpfile =~ s/\./_$self->{fileid}./ if $self->{fileid}; + $self->{tmpfile} = "$userspath/${fileid}_${tmpfile}"; + + if ($self->{format} =~ /(postscript|pdf)/ || $self->{media} eq 'email') { + $out = $self->{OUT}; + $self->{OUT} = ">$self->{tmpfile}"; + } + + if ($self->{OUT}) { + open(OUT, "$self->{OUT}") or $self->error("$self->{OUT} : $!"); + + } else { + open(OUT, ">-") or $self->error("STDOUT : $!"); + $self->header; + } + + # first we generate a tmpfile + # read file and replace <%variable%> + while ($_ = shift) { + + $par = ""; + $var = $_; + + # detect pagebreak block and its parameters + if (/<%pagebreak ([0-9]+) ([0-9]+) ([0-9]+)%>/) { + $chars_per_line = $1; + $lines_on_first_page = $2; + $lines_on_second_page = $3; + + while ($_ = shift) { + last if (/<%end pagebreak%>/); + $pagebreak .= $_; + } + } + + if (/<%foreach /) { + + # this one we need for the count + chomp $var; + $var =~ s/.*?<%foreach (.+?)%>/$1/; + while ($_ = shift) { + last if (/<%end $var%>/); + + # store line in $par + $par .= $_; + } + + # display contents of $self->{number}[] array + for $i (0 .. $#{ $self->{$var} }) { + + if ($var =~ /^(part|service)$/) { + next if $self->{$var}[$i] eq 'NULL'; + } + + # Try to detect whether a manual page break is necessary + # but only if there was a <%pagebreak ...%> block before + + if ($var eq 'number' || $var eq 'part' || $var eq 'service') { + + if ($chars_per_line && defined $self->{$var}) { + + my $line; + my $lines = 0; + my @d = (description); + push @d, "itemnotes" if $self->{countitemnotes}; + + foreach my $item (@d) { + + if ($self->{$item}[$i]) { + + foreach $line (split /\r?\n/, $self->{$item}[$i]) { + $lines++; + $lines += int(length($line) / $chars_per_line); + } + } + } + + my $lpp; + + if ($current_page == 1) { + $lpp = $lines_on_first_page; + } else { + $lpp = $lines_on_second_page; + } + + # Yes we need a manual page break + if (($current_line + $lines) > $lpp) { + my $pb = $pagebreak; + + # replace the special variables <%sumcarriedforward%> + # and <%lastpage%> + my $psum = $self->format_amount($myconfig, $sum, 2); + $pb =~ s/<%sumcarriedforward%>/$psum/g; + $pb =~ s/<%lastpage%>/$current_page/g; + + # only "normal" variables are supported here + # (no <%if, no <%foreach, no <%include) + $pb =~ s/<%(.+?)%>/$self->{$1}/g; + + # page break block is ready to rock + print(OUT $pb); + $current_page++; + $current_line = 1; + $lines = 0; + } + + $current_line += $lines; + } + + $sum += $self->parse_amount($myconfig, $self->{linetotal}[$i]); + } + + # don't parse par, we need it for each line + print OUT $self->format_line($par, $i); + } + next; + } + + # if not comes before if! + if (/<%if not /) { + + # check if it is not set and display + chop; + s/.*?<%if not (.+?)%>/$1/; + + if (! $self->{$_}) { + + while ($_ = shift) { + last if (/<%end /); + + # store line in $par + $par .= $_; + } + + $_ = $par; + + } else { + + while ($_ = shift) { + last if (/<%end /); + } + + next; + } + } + + if (/<%if /) { + + # check if it is set and display + chop; + s/.*?<%if (.+?)%>/$1/; + + if (/\s/) { + @a = split; + $ok = eval "$self->{$a[0]} $a[1] $a[2]"; + } else { + $ok = $self->{$_}; + } + + if ($ok) { + while ($_ = shift) { + last if (/<%end /); + # store line in $par + $par .= $_; + } + + $_ = $par; + + } else { + + while ($_ = shift) { + last if (/<%end /); + } + + next; + } + } + + # check for <%include filename%> + if (/<%include /) { + + # get the filename + chomp $var; + $var =~ s/.*?<%include (.+?)%>/$1/; + + # remove / .. for security reasons + $var =~ s/(\/|\.\.)//g; + + # assume loop after 10 includes of the same file + next if ($include{$var} > 10); + + unless (open(INC, "$self->{templates}/$self->{language_code}/$var")) { + $err = $!; + $self->cleanup; + $self->error("$self->{templates}/$self->{language_code}/$var : $err"); + } + + unshift(@_, <INC>); + close(INC); + + $include{$var}++; + + next; + } + + print OUT $self->format_line($_); + + } + + close(OUT); + + delete $self->{countitemnotes}; + + # Convert the tex file to postscript + if ($self->{format} =~ /(postscript|pdf)/) { + + use Cwd; + $self->{cwd} = cwd(); + $self->{tmpdir} = "$self->{cwd}/$userspath"; + + unless (chdir("$userspath")) { + $err = $!; + $self->cleanup; + $self->error("chdir : $err"); + } + + $self->{tmpfile} =~ s/$userspath\///g; + + $self->{errfile} = $self->{tmpfile}; + $self->{errfile} =~ s/tex$/err/; + + my $r = 1; + if ($self->{format} eq 'postscript') { + + system("latex --interaction=nonstopmode $self->{tmpfile} > $self->{errfile}"); + + while ($self->rerun_latex) { + system("latex --interaction=nonstopmode $self->{tmpfile} > $self->{errfile}"); + last if ++$r > 4; + } + + $self->{tmpfile} =~ s/tex$/dvi/; + $self->error($self->cleanup) if ! (-f $self->{tmpfile}); + + system("dvips $self->{tmpfile} -o -q"); + $self->error($self->cleanup."dvips : $!") if ($?); + $self->{tmpfile} =~ s/dvi$/ps/; + } + + if ($self->{format} eq 'pdf') { + system("pdflatex --interaction=nonstopmode $self->{tmpfile} > $self->{errfile}"); + + while ($self->rerun_latex) { + system("pdflatex --interaction=nonstopmode $self->{tmpfile} > $self->{errfile}"); + last if ++$r > 4; + } + + $self->{tmpfile} =~ s/tex$/pdf/; + $self->error($self->cleanup) if ! (-f $self->{tmpfile}); + } + } + + + if ($self->{format} =~ /(postscript|pdf)/ || $self->{media} eq 'email') { + + if ($self->{media} eq 'email') { + + use LedgerSMB::Mailer; + + my $mail = new Mailer; + + for (qw(cc bcc subject message version format charset)) { + $mail->{$_} = $self->{$_} + } + + $mail->{to} = qq|$self->{email}|; + $mail->{from} = qq|"$myconfig->{name}" <$myconfig->{email}>|; + $mail->{fileid} = "$fileid."; + + # if we send html or plain text inline + if (($self->{format} =~ /(html|txt)/) && + ($self->{sendmode} eq 'inline')) { + + my $br = ""; + $br = "<br>" if $self->{format} eq 'html'; + + $mail->{contenttype} = "text/$self->{format}"; + + $mail->{message} =~ s/\r?\n/$br\n/g; + $myconfig->{signature} =~ s/\\n/$br\n/g; + $mail->{message} .= "$br\n-- $br\n$myconfig->{signature}\n$br" if $myconfig->{signature}; + + unless (open(IN, $self->{tmpfile})) { + $err = $!; + $self->cleanup; + $self->error("$self->{tmpfile} : $err"); + } + + while (<IN>) { + $mail->{message} .= $_; + } + + close(IN); + + } else { + + @{ $mail->{attachments} } = ($self->{tmpfile}); + + $myconfig->{signature} =~ s/\\n/\n/g; + $mail->{message} .= "\n-- \n$myconfig->{signature}" if $myconfig->{signature}; + + } + + if ($err = $mail->send($out)) { + $self->cleanup; + $self->error($err); + } + + } else { + + $self->{OUT} = $out; + + unless (open(IN, $self->{tmpfile})) { + $err = $!; + $self->cleanup; + $self->error("$self->{tmpfile} : $err"); + } + + binmode(IN); + + $self->{copies} = 1 if $self->{media} =~ /(screen|email|queue)/; + + chdir("$self->{cwd}"); + + for my $i (1 .. $self->{copies}) { + if ($self->{OUT}) { + + unless (open(OUT, $self->{OUT})) { + $err = $!; + $self->cleanup; + $self->error("$self->{OUT} : $err"); + } + + } else { + + # launch application + print qq|Content-Type: application/$self->{format} + Content-Disposition: attachment; filename="$self->{tmpfile}"\n\n|; + + unless (open(OUT, ">-")) { + $err = $!; + $self->cleanup; + $self->error("STDOUT : $err"); + } + } + + binmode(OUT); + + while (<IN>) { + print OUT $_; + } + + close(OUT); + seek IN, 0, 0; + } + + close(IN); + } + + $self->cleanup; + } +} + + +sub format_line { + + my $self = shift; + + $_ = shift; + my $i = shift; + + my $str; + my $newstr; + my $pos; + my $l; + my $lf; + my $line; + my $var = ""; + my %a; + my $offset; + my $pad; + my $item; + + while (/<%(.+?)%>/) { + + %a = (); + + foreach $item (split / /, $1) { + my ($key, $value) = split /=/, $item; + + if ($value ne "") { + $a{$key} = $value; + } else { + $var = $item; + } + } + + $str = (defined $i) ? $self->{$var}[$i] : $self->{$var}; + $newstr = $str; + + $self->{countitemnotes} = 1 if $var eq 'itemnotes'; + + $var = $1; + if ($var =~ /^if\s+not\s+/) { + + if ($str) { + + $var =~ s/if\s+not\s+//; + s/<%if\s+not\s+$var%>.*?(<%end\s+$var%>|$)//s; + + } else { + s/<%$var%>//; + } + + next; + } + + if ($var =~ /^if\s+/) { + + if ($str) { + s/<%$var%>//; + } else { + $var =~ s/if\s+//; + s/<%if\s+$var%>.*?(<%end\s+$var%>|$)//s; + } + + next; + } + + if ($var =~ /^end\s+/) { + s/<%$var%>//; + next; + } + + if ($a{align} || $a{width} || $a{offset}) { + + $newstr = ""; + $offset = 0; + $lf = ""; + + foreach $str (split /\n/, $str) { + + $line = $str; + $l = length $str; + + do { + + if (($pos = length $str) > $a{width}) { + + if (($pos = rindex $str, " ", $a{width}) > 0) { + $line = substr($str, 0, $pos); + } + + $pos = length $str if $pos == -1; + } + + $l = length $line; + + # pad left, right or center + $l = ($a{width} - $l); + + $pad = " " x $l; + + if ($a{align} =~ /right/i) { + $line = " " x $offset . $pad . $line; + } + + if ($a{align} =~ /left/i) { + $line = " " x $offset . $line . $pad; + } + + if ($a{align} =~ /center/i) { + $pad = " " x ($l/2); + $line = " " x $offset . $pad . $line; + $pad = " " x ($l/2); + $line .= $pad; + } + + $newstr .= "$lf$line"; + + $str = substr($str, $pos + 1); + $line = $str; + $lf = "\n"; + + $offset = $a{offset}; + + } while ($str); + } + } + + s/<%(.+?)%>/$newstr/; + + } + + $_; +} + + +sub cleanup { + + my $self = shift; + + chdir("$self->{tmpdir}"); + + my @err = (); + + if (-f "$self->{errfile}") { + open(FH, "$self->{errfile}"); + @err = <FH>; + close(FH); + } + + if ($self->{tmpfile}) { + # strip extension + $self->{tmpfile} =~ s/\.\w+$//g; + my $tmpfile = $self->{tmpfile}; + unlink(<$tmpfile.*>); + } + + chdir("$self->{cwd}"); + + "@err"; +} + + +sub rerun_latex { + + my $self = shift; + + my $a = 0; + + if (-f "$self->{errfile}") { + open(FH, "$self->{errfile}"); + $a = grep /(longtable Warning:|Warning:.*?LastPage)/, <FH>; + close(FH); + } + + $a; +} + + +sub format_string { + + my ($self, @fields) = @_; + + my $format = $self->{format}; + + if ($self->{format} =~ /(postscript|pdf)/) { + $format = 'tex'; + } + + my %replace = ( 'order' => { html => [ '<', '>', '\n', '\r' ], + txt => [ '\n', '\r' ], + tex => [ quotemeta('\\'), '&', '\n', + '\r', '\$', '%', '_', '#', + quotemeta('^'), '{', '}', '<', '>', + '?' ], + utf => [ quotemeta('\\'), '&', quotemeta('\n'), + '\r', '\$', '%', '_', '#', + quotemeta('^'), '{', '}', '<', '>' ] }, + html => { '<' => '<', '>' => '>', + '\n' => '<br />', '\r' => '<br />' }, + txt => { '\n' => "\n", '\r' => "\r" }, + tex => {'&' => '\&', '\$' => '\$', '%' => '\%', '_' => '\_', + '#' => '\#', quotemeta('^') => '\^\\', '{' => '\{', '}' => '\}', + '<' => '$<$', '>' => '$>$', + '\n' => '\newline ', '\r' => '\newline ', + '?' => '\pounds ', quotemeta('\\') => '/' } ); + + my $key; + + foreach $key (@{ $replace{order}{$format} }) { + for (@fields) { $self->{$_} =~ s/$key/$replace{$format}{$key}/g } + } + +} + + +sub datetonum { + + my ($self, $myconfig, $date, $picture) = @_; + + if ($date && $date =~ /\D/) { + + if ($myconfig->{dateformat} =~ /^yy/) { + ($yy, $mm, $dd) = split /\D/, $date; + } + + if ($myconfig->{dateformat} =~ /^mm/) { + ($mm, $dd, $yy) = split /\D/, $date; + } + + if ($myconfig->{dateformat} =~ /^dd/) { + ($dd, $mm, $yy) = split /\D/, $date; + } + + $dd *= 1; + $mm *= 1; + $yy += 2000 if length $yy == 2; + + $dd = substr("0$dd", -2); + $mm = substr("0$mm", -2); + + $date = "$yy$mm$dd"; + } + + $date; +} + + +sub add_date { + + my ($self, $myconfig, $date, $repeat, $unit) = @_; + + use Time::Local; + + my $diff = 0; + my $spc = $myconfig->{dateformat}; + $spc =~ s/\w//g; + $spc = substr($spc, 0, 1); + + if ($date) { + + if ($date =~ /\D/) { + + if ($myconfig->{dateformat} =~ /^yy/) { + ($yy, $mm, $dd) = split /\D/, $date; + } + + if ($myconfig->{dateformat} =~ /^mm/) { + ($mm, $dd, $yy) = split /\D/, $date; + } + + if ($myconfig->{dateformat} =~ /^dd/) { + ($dd, $mm, $yy) = split /\D/, $date; + } + + } else { + # ISO + ($yy, $mm, $dd) =~ /(....)(..)(..)/; + } + + if ($unit eq 'days') { + $diff = $repeat * 86400; + } + + if ($unit eq 'weeks') { + $diff = $repeat * 604800; + } + + if ($unit eq 'months') { + $diff = $mm + $repeat; + + my $whole = int($diff / 12); + $yy += $whole; + + $mm = ($diff % 12) + 1; + $diff = 0; + } + + if ($unit eq 'years') { + $yy++; + } + + $mm--; + + @t = localtime(timelocal(0,0,0,$dd,$mm,$yy) + $diff); + + $t[4]++; + $mm = substr("0$t[4]",-2); + $dd = substr("0$t[3]",-2); + $yy = $t[5] + 1900; + + if ($date =~ /\D/) { + + if ($myconfig->{dateformat} =~ /^yy/) { + $date = "$yy$spc$mm$spc$dd"; + } + + if ($myconfig->{dateformat} =~ /^mm/) { + $date = "$mm$spc$dd$spc$yy"; + } + + if ($myconfig->{dateformat} =~ /^dd/) { + $date = "$dd$spc$mm$spc$yy"; + } + + } else { + $date = "$yy$mm$dd"; + } + } + + $date; +} + + +sub print_button { + my ($self, $button, $name) = @_; + + print qq|<input class="submit" type="submit" name="action" value="$button->{$name}{value}" accesskey="$button->{$name}{key}" title="$button->{$name}{value} [Alt-$button->{$name}{key}]" />\n|; +} + + +# Database routines used throughout + +sub dbconnect { + + my ($self, $myconfig) = @_; + + # connect to database + my $dbh = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}) or $self->dberror; + + # set db options + if ($myconfig->{dboptions}) { + $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions}); + } + + $dbh; +} + + +sub dbconnect_noauto { + + my ($self, $myconfig) = @_; + + # connect to database + $dbh = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, {AutoCommit => 0}) or $self->dberror; + + # set db options + if ($myconfig->{dboptions}) { + $dbh->do($myconfig->{dboptions}); + } + + $dbh; +} + + +sub dbquote { + + my ($self, $var, $type) = @_; + + # DBI does not return NULL for SQL_DATE if the date is empty + if ($type eq 'SQL_DATE') { + $_ = ($var) ? "'$var'" : "NULL"; + } + + if ($type eq 'SQL_INT') { + $_ = $var * 1; + } + + $_; +} + + +sub update_balance { + + my ($self, $dbh, $table, $field, $where, $value) = @_; + + # if we have a value, go do it + if ($value) { + # retrieve balance from table + my $query = "SELECT $field FROM $table WHERE $where FOR UPDATE"; + my ($balance) = $dbh->selectrow_array($query); + + $balance += $value; + # update balance + $query = "UPDATE $table SET $field = $balance WHERE $where"; + $dbh->do($query) || $self->dberror($query); + } +} + + +sub update_exchangerate { + + my ($self, $dbh, $curr, $transdate, $buy, $sell) = @_; + + # some sanity check for currency + return if ($curr eq ""); + + my $query = qq|SELECT curr + FROM exchangerate + WHERE curr = '$curr' + AND transdate = '$transdate' + FOR UPDATE|; + + my $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + my $set; + + if ($buy && $sell) { + $set = "buy = $buy, sell = $sell"; + } elsif ($buy) { + $set = "buy = $buy"; + } elsif ($sell) { + $set = "sell = $sell"; + } + + if ($sth->fetchrow_array) { + $query = qq|UPDATE exchangerate + SET $set + WHERE curr = '$curr' + AND transdate = '$transdate'|; + + } else { + $query = qq|INSERT INTO exchangerate (curr, buy, sell, transdate) + VALUES ('$curr', $buy, $sell, '$transdate')|; + } + + $sth->finish; + $dbh->do($query) || $self->dberror($query); + +} + + +sub save_exchangerate { + + my ($self, $myconfig, $currency, $transdate, $rate, $fld) = @_; + + my $dbh = $self->dbconnect($myconfig); + + my ($buy, $sell) = (0, 0); + $buy = $rate if $fld eq 'buy'; + $sell = $rate if $fld eq 'sell'; + + $self->update_exchangerate($dbh, $currency, $transdate, $buy, $sell); + + $dbh->disconnect; +} + + +sub get_exchangerate { + + my ($self, $dbh, $curr, $transdate, $fld) = @_; + + my $exchangerate = 1; + + if ($transdate) { + my $query = qq|SELECT $fld + FROM exchangerate + WHERE curr = '$curr' + AND transdate = '$transdate'|; + + ($exchangerate) = $dbh->selectrow_array($query); + } + + $exchangerate; +} + + +sub check_exchangerate { + + my ($self, $myconfig, $currency, $transdate, $fld) = @_; + + return "" unless $transdate; + + my $dbh = $self->dbconnect($myconfig); + + my $query = qq|SELECT $fld + FROM exchangerate + WHERE curr = '$currency' + AND transdate = '$transdate'|; + + my ($exchangerate) = $dbh->selectrow_array($query); + + $dbh->disconnect; + + $exchangerate; +} + + +sub add_shipto { + my ($self, $dbh, $id) = @_; + + my $shipto; + + foreach my $item (qw(name address1 address2 city state + zipcode country contact phone fax email)) { + + if ($self->{"shipto$item"} ne "") { + $shipto = 1 if ($self->{$item} ne $self->{"shipto$item"}); + } + } + + if ($shipto) { + my $query = qq|INSERT INTO shipto (trans_id, shiptoname, shiptoaddress1, + shiptoaddress2, shiptocity, shiptostate, + shiptozipcode, shiptocountry, shiptocontact, + shiptophone, shiptofax, shiptoemail) + VALUES ($id, | + .$dbh->quote($self->{shiptoname}).qq|, | + .$dbh->quote($self->{shiptoaddress1}).qq|, | + .$dbh->quote($self->{shiptoaddress2}).qq|, | + .$dbh->quote($self->{shiptocity}).qq|, | + .$dbh->quote($self->{shiptostate}).qq|, | + .$dbh->quote($self->{shiptozipcode}).qq|, | + .$dbh->quote($self->{shiptocountry}).qq|, | + .$dbh->quote($self->{shiptocontact}).qq|, + '$self->{shiptophone}', '$self->{shiptofax}', + '$self->{shiptoemail}')|; + + $dbh->do($query) || $self->dberror($query); + } +} + + +sub get_employee { + my ($self, $dbh) = @_; + + my $login = $self->{login}; + $login =~ s/@.*//; + + my $query = qq|SELECT name, id + FROM employee + WHERE login = '$login'|; + + my (@a) = $dbh->selectrow_array($query); + $a[1] *= 1; + + @a; +} + + +# this sub gets the id and name from $table +sub get_name { + + my ($self, $myconfig, $table, $transdate) = @_; + + # connect to database + my $dbh = $self->dbconnect($myconfig); + + my $where; + if ($transdate) { + $where = qq|AND (startdate IS NULL OR startdate <= '$transdate') + AND (enddate IS NULL OR enddate >= '$transdate')|; + } + + my $name = $self->like(lc $self->{$table}); + + my $query = qq|SELECT * + FROM $table + WHERE (lower(name) LIKE '$name' + OR ${table}number LIKE '$name') + $where + ORDER BY name|; + + my $sth = $dbh->prepare($query); + + $sth->execute || $self->dberror($query); + + my $i = 0; + @{ $self->{name_list} } = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push(@{ $self->{name_list} }, $ref); + $i++; + } + + $sth->finish; + $dbh->disconnect; + + $i; + +} + + +sub all_vc { + + my ($self, $myconfig, $vc, $module, $dbh, $transdate, $job) = @_; + + my $ref; + my $disconnect = 0; + + if (! $dbh) { + $dbh = $self->dbconnect($myconfig); + $disconnect = 1; + } + + my $sth; + + my $query = qq|SELECT count(*) FROM $vc|; + my $where; + + if ($transdate) { + $where = qq|AND (startdate IS NULL OR startdate <= '$transdate') + AND (enddate IS NULL OR enddate >= '$transdate')|; + + $query .= qq| WHERE 1=1 $where|; + } + + my ($count) = $dbh->selectrow_array($query); + + # build selection list + if ($count < $myconfig->{vclimit}) { + + $self->{"${vc}_id"} *= 1; + + $query = qq|SELECT id, name + FROM $vc + WHERE 1=1 + $where + + UNION + + SELECT id,name + FROM $vc + WHERE id = $self->{"${vc}_id"} + ORDER BY name|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + @{ $self->{"all_$vc"} } = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $self->{"all_$vc"} }, $ref; + } + + $sth->finish; + + } + + # get self + if (! $self->{employee_id}) { + ($self->{employee}, $self->{employee_id}) = split /--/, $self->{employee}; + ($self->{employee}, $self->{employee_id}) = $self->get_employee($dbh) unless $self->{employee_id}; + } + + $self->all_employees($myconfig, $dbh, $transdate, 1); + + $self->all_departments($myconfig, $dbh, $vc); + + $self->all_projects($myconfig, $dbh, $transdate, $job); + + # get language codes + $query = qq|SELECT * + FROM language + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $self->{all_language} = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $self->{all_language} }, $ref; + } + + $sth->finish; + $self->all_taxaccounts($myconfig, $dbh, $transdate); + $dbh->disconnect if $disconnect; +} + + +sub all_taxaccounts { + + my ($self, $myconfig, $dbh, $transdate) = @_; + + my $disconnect = ($dbh) ? 0 : 1; + + if (! $dbh) { + $dbh = $self->dbconnect($myconfig); + } + + my $sth; + my $query; + my $where; + + + if ($transdate) { + $where = qq| AND (t.validto >= '$transdate' OR t.validto IS NULL)|; + } + + if ($self->{taxaccounts}) { + + # rebuild tax rates + $query = qq|SELECT t.rate, t.taxnumber + FROM tax t + JOIN chart c ON (c.id = t.chart_id) + WHERE c.accno = ? + $where + ORDER BY accno, validto|; + + $sth = $dbh->prepare($query) || $self->dberror($query); + + foreach my $accno (split / /, $self->{taxaccounts}) { + $sth->execute($accno); + ($self->{"${accno}_rate"}, $self->{"${accno}_taxnumber"}) = $sth->fetchrow_array; + $sth->finish; + } + } + + $dbh->disconnect if $disconnect; +} + + +sub all_employees { + + my ($self, $myconfig, $dbh, $transdate, $sales) = @_; + + # setup employees/sales contacts + my $query = qq|SELECT id, name + FROM employee + WHERE 1 = 1|; + + if ($transdate) { + $query .= qq| AND (startdate IS NULL OR startdate <= '$transdate') + AND (enddate IS NULL OR enddate >= '$transdate')|; + } else { + $query .= qq| AND enddate IS NULL|; + } + + if ($sales) { + $query .= qq| AND sales = '1'|; + } + + $query .= qq| ORDER BY name|; + my $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $self->{all_employee} }, $ref; + } + + $sth->finish; +} + + + +sub all_projects { + + my ($self, $myconfig, $dbh, $transdate, $job) = @_; + + my $disconnect = 0; + + if (! $dbh) { + $dbh = $self->dbconnect($myconfig); + $disconnect = 1; + } + + my $where = "1 = 1"; + + $where = qq|id NOT IN (SELECT id + FROM parts + WHERE project_id > 0)| if ! $job; + + my $query = qq|SELECT * + FROM project + WHERE $where|; + + if ($form->{language_code}) { + + $query = qq|SELECT pr.*, t.description AS translation + FROM project pr + LEFT JOIN translation t ON (t.trans_id = pr.id) + WHERE t.language_code = '$form->{language_code}'|; + } + + if ($transdate) { + $query .= qq| AND (startdate IS NULL OR startdate <= '$transdate') + AND (enddate IS NULL OR enddate >= '$transdate')|; + } + + $query .= qq| ORDER BY projectnumber|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + @{ $self->{all_project} } = (); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $self->{all_project} }, $ref; + } + + $sth->finish; + $dbh->disconnect if $disconnect; +} + + +sub all_departments { + + my ($self, $myconfig, $dbh, $vc) = @_; + + my $disconnect = 0; + if (! $dbh) { + $dbh = $self->dbconnect($myconfig); + $disconnect = 1; + } + + my $where = "1 = 1"; + + if ($vc) { + if ($vc eq 'customer') { + $where = " role = 'P'"; + } + } + + my $query = qq|SELECT id, description + FROM department + WHERE $where + ORDER BY 2|; + + my $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + @{ $self->{all_department} } = (); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $self->{all_department} }, $ref; + } + + $sth->finish; + $self->all_years($myconfig, $dbh); + $dbh->disconnect if $disconnect; +} + + +sub all_years { + + my ($self, $myconfig, $dbh) = @_; + + my $disconnect = 0; + + if (! $dbh) { + $dbh = $self->dbconnect($myconfig); + $disconnect = 1; + } + + # get years + my $query = qq|SELECT (SELECT MIN(transdate) FROM acc_trans), + (SELECT MAX(transdate) FROM acc_trans) + FROM defaults|; + + my ($startdate, $enddate) = $dbh->selectrow_array($query); + + if ($myconfig->{dateformat} =~ /^yy/) { + ($startdate) = split /\W/, $startdate; + ($enddate) = split /\W/, $enddate; + } else { + (@_) = split /\W/, $startdate; + $startdate = $_[2]; + (@_) = split /\W/, $enddate; + $enddate = $_[2]; + } + + $self->{all_years} = (); + $startdate = substr($startdate,0,4); + $enddate = substr($enddate,0,4); + + while ($enddate >= $startdate) { + push @{ $self->{all_years} }, $enddate--; + } + + #this should probably be changed to use locale + %{ $self->{all_month} } = ( '01' => 'January', + '02' => 'February', + '03' => 'March', + '04' => 'April', + '05' => 'May ', + '06' => 'June', + '07' => 'July', + '08' => 'August', + '09' => 'September', + '10' => 'October', + '11' => 'November', + '12' => 'December' ); + + $dbh->disconnect if $disconnect; +} + + +sub create_links { + + my ($self, $module, $myconfig, $vc, $job) = @_; + + # get last customers or vendors + my ($query, $sth); + + my $dbh = $self->dbconnect($myconfig); + + my %xkeyref = (); + + + # now get the account numbers + $query = qq|SELECT accno, description, link + FROM chart + WHERE link LIKE '%$module%' + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $self->{accounts} = ""; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + foreach my $key (split /:/, $ref->{link}) { + + if ($key =~ /$module/) { + # cross reference for keys + $xkeyref{$ref->{accno}} = $key; + + push @{ $self->{"${module}_links"}{$key} }, { accno => $ref->{accno}, + description => $ref->{description} }; + + $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/; + } + } + } + + $sth->finish; + + my $arap = ($vc eq 'customer') ? 'ar' : 'ap'; + + if ($self->{id}) { + + $query = qq|SELECT a.invnumber, a.transdate, + a.${vc}_id, a.datepaid, a.duedate, a.ordnumber, + a.taxincluded, a.curr AS currency, a.notes, a.intnotes, + c.name AS $vc, a.department_id, d.description AS department, + a.amount AS oldinvtotal, a.paid AS oldtotalpaid, + a.employee_id, e.name AS employee, c.language_code, + a.ponumber + FROM $arap a + JOIN $vc c ON (a.${vc}_id = c.id) + LEFT JOIN employee e ON (e.id = a.employee_id) + LEFT JOIN department d ON (d.id = a.department_id) + WHERE a.id = $self->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + + foreach $key (keys %$ref) { + $self->{$key} = $ref->{$key}; + } + + $sth->finish; + + + # get printed, emailed + $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname + FROM status s + WHERE s.trans_id = $self->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $self->{printed} .= "$ref->{formname} " if $ref->{printed}; + $self->{emailed} .= "$ref->{formname} " if $ref->{emailed}; + $self->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile}; + } + + $sth->finish; + for (qw(printed emailed queued)) { $self->{$_} =~ s/ +$//g } + + # get recurring + $self->get_recurring($dbh); + + # get amounts from individual entries + $query = qq|SELECT c.accno, c.description, a.source, a.amount, + a.memo, a.transdate, a.cleared, a.project_id, + p.projectnumber + FROM acc_trans a + JOIN chart c ON (c.id = a.chart_id) + LEFT JOIN project p ON (p.id = a.project_id) + WHERE a.trans_id = $self->{id} + AND a.fx_transaction = '0' + ORDER BY transdate|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + + my $fld = ($vc eq 'customer') ? 'buy' : 'sell'; + + $self->{exchangerate} = $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld); + + # store amounts in {acc_trans}{$key} for multiple accounts + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{exchangerate} = $self->get_exchangerate($dbh, $self->{currency}, $ref->{transdate}, $fld); + + push @{ $self->{acc_trans}{$xkeyref{$ref->{accno}}} }, $ref; + } + + $sth->finish; + + $query = qq|SELECT d.curr AS currencies, d.closedto, d.revtrans + FROM defaults d|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $self->{$_} = $ref->{$_} } + $sth->finish; + + } else { + + # get date + $query = qq|SELECT current_date AS transdate, + d.curr AS currencies, d.closedto, d.revtrans + FROM defaults d|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $self->{$_} = $ref->{$_} } + $sth->finish; + + if (! $self->{"$self->{vc}_id"}) { + $self->lastname_used($myconfig, $dbh, $vc, $module); + } + } + + $self->all_vc($myconfig, $vc, $module, $dbh, $self->{transdate}, $job); + $dbh->disconnect; +} + + +sub lastname_used { + + my ($self, $myconfig, $dbh, $vc, $module) = @_; + + my $arap = ($vc eq 'customer') ? "ar" : "ap"; + my $where = "1 = 1"; + my $sth; + + if ($self->{type} =~ /_order/) { + $arap = 'oe'; + $where = "quotation = '0'"; + } + + if ($self->{type} =~ /_quotation/) { + $arap = 'oe'; + $where = "quotation = '1'"; + } + + my $query = qq|SELECT id + FROM $arap + WHERE id IN (SELECT MAX(id) + FROM $arap + WHERE $where + AND ${vc}_id > 0)|; + + my ($trans_id) = $dbh->selectrow_array($query); + + $trans_id *= 1; + + my $DAYS = ($myconfig->{dbdriver} eq 'DB2') ? "DAYS" : ""; + + $query = qq|SELECT ct.name AS $vc, a.curr AS currency, a.${vc}_id, + current_date + ct.terms $DAYS AS duedate, a.department_id, + d.description AS department, ct.notes, ct.curr AS currency + FROM $arap a + JOIN $vc ct ON (a.${vc}_id = ct.id) + LEFT JOIN department d ON (a.department_id = d.id) + WHERE a.id = $trans_id|; + + $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $self->{$_} = $ref->{$_} } + $sth->finish; +} + + + +sub current_date { + + my ($self, $myconfig, $thisdate, $days) = @_; + + my $dbh = $self->dbconnect($myconfig); + my $query; + + $days *= 1; + if ($thisdate) { + + my $dateformat = $myconfig->{dateformat}; + + if ($myconfig->{dateformat} !~ /^y/) { + my @a = split /\D/, $thisdate; + $dateformat .= "yy" if (length $a[2] > 2); + } + + if ($thisdate !~ /\D/) { + $dateformat = 'yyyymmdd'; + } + + if ($myconfig->{dbdriver} eq 'DB2') { + $query = qq|SELECT date('$thisdate') + $days DAYS AS thisdate + FROM defaults|; + + } else { + $query = qq|SELECT to_date('$thisdate', '$dateformat') + $days AS thisdate + FROM defaults|; + } + + } else { + $query = qq|SELECT current_date AS thisdate + FROM defaults|; + } + + ($thisdate) = $dbh->selectrow_array($query); + $dbh->disconnect; + $thisdate; +} + + +sub like { + + my ($self, $str) = @_; + + if ($str !~ /(%|_)/) { + + if ($str =~ /(^").*("$)/) { + $str =~ s/(^"|"$)//g; + } else { + $str = "%$str%"; + } + } + + $str =~ s/'/''/g; + $str; +} + + +sub redo_rows { + + my ($self, $flds, $new, $count, $numrows) = @_; + + my @ndx = (); + + for (1 .. $count) { + push @ndx, { num => $new->[$_-1]->{runningnumber}, ndx => $_ } + } + + my $i = 0; + # fill rows + foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) { + $i++; + $j = $item->{ndx} - 1; + for (@{$flds}) { $self->{"${_}_$i"} = $new->[$j]->{$_} } + } + + # delete empty rows + for $i ($count + 1 .. $numrows) { + for (@{$flds}) { delete $self->{"${_}_$i"} } + } +} + + +sub get_partsgroup { + + my ($self, $myconfig, $p) = @_; + + my $dbh = $self->dbconnect($myconfig); + + my $query = qq|SELECT DISTINCT pg.id, pg.partsgroup + FROM partsgroup pg + JOIN parts p ON (p.partsgroup_id = pg.id)|; + + my $where; + my $sortorder = "partsgroup"; + + if ($p->{searchitems} eq 'part') { + $where = qq| WHERE (p.inventory_accno_id > 0 + AND p.income_accno_id > 0)|; + } + + if ($p->{searchitems} eq 'service') { + $where = qq| WHERE p.inventory_accno_id IS NULL|; + } + + if ($p->{searchitems} eq 'assembly') { + $where = qq| WHERE p.assembly = '1'|; + } + + if ($p->{searchitems} eq 'labor') { + $where = qq| WHERE p.inventory_accno_id > 0 AND p.income_accno_id IS NULL|; + } + + if ($p->{searchitems} eq 'nolabor') { + $where = qq| WHERE p.income_accno_id > 0|; + } + + if ($p->{all}) { + $query = qq|SELECT id, partsgroup + FROM partsgroup|; + } + + if ($p->{language_code}) { + $sortorder = "translation"; + + $query = qq|SELECT DISTINCT pg.id, pg.partsgroup, + t.description AS translation + FROM partsgroup pg + JOIN parts p ON (p.partsgroup_id = pg.id) + LEFT JOIN translation t ON (t.trans_id = pg.id AND t.language_code = '$p->{language_code}')|; + } + + $query .= qq| $where ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $self->dberror($query); + + $self->{all_partsgroup} = (); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $self->{all_partsgroup} }, $ref; + } + + $sth->finish; + $dbh->disconnect; +} + + +sub update_status { + + my ($self, $myconfig) = @_; + + # no id return + return unless $self->{id}; + + my $dbh = $self->dbconnect_noauto($myconfig); + + my %queued = split / +/, $self->{queued}; + my $spoolfile = ($queued{$self->{formname}}) ? "'$queued{$self->{formname}}'" : 'NULL'; + + my $query = qq|DELETE FROM status + WHERE formname = '$self->{formname}' + AND trans_id = $self->{id}|; + + $dbh->do($query) || $self->dberror($query); + + my $printed = ($self->{printed} =~ /$self->{formname}/) ? "1" : "0"; + my $emailed = ($self->{emailed} =~ /$self->{formname}/) ? "1" : "0"; + + $query = qq|INSERT INTO status (trans_id, printed, emailed, + spoolfile, formname) + VALUES ($self->{id}, '$printed', + '$emailed', $spoolfile, + '$self->{formname}')|; + + $dbh->do($query) || $self->dberror($query); + $dbh->commit; + $dbh->disconnect; +} + + +sub save_status { + + my ($self, $dbh) = @_; + + my $formnames = $self->{printed}; + my $emailforms = $self->{emailed}; + + my $query = qq|DELETE FROM status + WHERE trans_id = $self->{id}|; + + $dbh->do($query) || $self->dberror($query); + + my %queued; + my $formname; + + if ($self->{queued}) { + + %queued = split / +/, $self->{queued}; + + foreach $formname (keys %queued) { + + $printed = ($self->{printed} =~ /$formname/) ? "1" : "0"; + $emailed = ($self->{emailed} =~ /$formname/) ? "1" : "0"; + + if ($queued{$formname}) { + $query = qq|INSERT INTO status (trans_id, printed, emailed, + spoolfile, formname) + VALUES ($self->{id}, '$printed', '$emailed', + '$queued{$formname}', '$formname')|; + + $dbh->do($query) || $self->dberror($query); + } + + $formnames =~ s/$formname//; + $emailforms =~ s/$formname//; + + } + } + + # save printed, emailed info + $formnames =~ s/^ +//g; + $emailforms =~ s/^ +//g; + + my %status = (); + for (split / +/, $formnames) { $status{$_}{printed} = 1 } + for (split / +/, $emailforms) { $status{$_}{emailed} = 1 } + + foreach my $formname (keys %status) { + $printed = ($formnames =~ /$self->{formname}/) ? "1" : "0"; + $emailed = ($emailforms =~ /$self->{formname}/) ? "1" : "0"; + + $query = qq|INSERT INTO status (trans_id, printed, emailed, formname) + VALUES ($self->{id}, '$printed', '$emailed', '$formname')|; + + $dbh->do($query) || $self->dberror($query); + } +} + + +sub get_recurring { + + my ($self, $dbh) = @_; + + my $query = qq/SELECT s.*, se.formname || ':' || se.format AS emaila, + se.message, + sp.formname || ':' || sp.format || ':' || sp.printer AS printa + FROM recurring s + LEFT JOIN recurringemail se ON (s.id = se.id) + LEFT JOIN recurringprint sp ON (s.id = sp.id) + WHERE s.id = $self->{id}/; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + for (qw(email print)) { $self->{"recurring$_"} = "" } + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + for (keys %$ref) { $self->{"recurring$_"} = $ref->{$_} } + $self->{recurringemail} .= "$ref->{emaila}:"; + $self->{recurringprint} .= "$ref->{printa}:"; + for (qw(emaila printa)) { delete $self->{"recurring$_"} } + } + + $sth->finish; + chop $self->{recurringemail}; + chop $self->{recurringprint}; + + if ($self->{recurringstartdate}) { + $self->{recurringreference} = $self->escape($self->{recurringreference},1); + $self->{recurringmessage} = $self->escape($self->{recurringmessage},1); + for (qw(reference startdate repeat unit howmany + payment print email message)) { + + $self->{recurring} .= qq|$self->{"recurring$_"},| + } + + chop $self->{recurring}; + } +} + + +sub save_recurring { + + my ($self, $dbh, $myconfig) = @_; + + my $disconnect = 0; + + if (! $dbh) { + $dbh = $self->dbconnect_noauto($myconfig); + $disconnect = 1; + } + + my $query; + + $query = qq|DELETE FROM recurring + WHERE id = $self->{id}|; + + $dbh->do($query) || $self->dberror($query); + + $query = qq|DELETE FROM recurringemail + WHERE id = $self->{id}|; + + $dbh->do($query) || $self->dberror($query); + + $query = qq|DELETE FROM recurringprint + WHERE id = $self->{id}|; + + $dbh->do($query) || $self->dberror($query); + + if ($self->{recurring}) { + + my %s = (); + ($s{reference}, $s{startdate}, $s{repeat}, $s{unit}, $s{howmany}, + $s{payment}, $s{print}, $s{email}, $s{message}) = split /,/, $self->{recurring}; + + for (qw(reference message)) { $s{$_} = $self->unescape($s{$_}) } + for (qw(repeat howmany payment)) { $s{$_} *= 1 } + + # calculate enddate + my $advance = $s{repeat} * ($s{howmany} - 1); + my %interval = ( 'Pg' => "(date '$s{startdate}' + interval '$advance $s{unit}')", + 'DB2' => qq|(date ('$s{startdate}') + "$advance $s{unit}")|, ); + + $interval{Oracle} = $interval{PgPP} = $interval{Pg}; + + $query = qq|SELECT $interval{$myconfig->{dbdriver}} + FROM defaults|; + + my ($enddate) = $dbh->selectrow_array($query); + + # calculate nextdate + $query = qq|SELECT current_date - date '$s{startdate}' AS a, + date '$enddate' - current_date AS b + FROM defaults|; + + my ($a, $b) = $dbh->selectrow_array($query); + + if ($a + $b) { + $advance = int(($a / ($a + $b)) * ($s{howmany} - 1) + 1) * $s{repeat}; + } else { + $advance = 0; + } + + my $nextdate = $enddate; + if ($advance > 0) { + if ($advance < ($s{repeat} * $s{howmany})) { + %interval = ( 'Pg' => "(date '$s{startdate}' + interval '$advance $s{unit}')", + 'DB2' => qq|(date ('$s{startdate}') + "$advance $s{unit}")|,); + + $interval{Oracle} = $interval{PgPP} = $interval{Pg}; + + $query = qq|SELECT $interval{$myconfig->{dbdriver}} + FROM defaults|; + + ($nextdate) = $dbh->selectrow_array($query); + } + + } else { + $nextdate = $s{startdate}; + } + + if ($self->{recurringnextdate}) { + + $nextdate = $self->{recurringnextdate}; + + $query = qq|SELECT '$enddate' - date '$nextdate' + FROM defaults|; + + if ($dbh->selectrow_array($query) < 0) { + undef $nextdate; + } + } + + $self->{recurringpayment} *= 1; + + $query = qq|INSERT INTO recurring (id, reference, startdate, enddate, + nextdate, repeat, unit, howmany, payment) + VALUES ($self->{id}, |.$dbh->quote($s{reference}).qq|, + '$s{startdate}', '$enddate', |. + $self->dbquote($nextdate, SQL_DATE). + qq|, $s{repeat}, '$s{unit}', $s{howmany}, '$s{payment}')|; + + $dbh->do($query) || $self->dberror($query); + + my @p; + my $p; + my $i; + my $sth; + + if ($s{email}) { + # formname:format + @p = split /:/, $s{email}; + + $query = qq|INSERT INTO recurringemail (id, formname, format, message) + VALUES ($self->{id}, ?, ?, ?)|; + + $sth = $dbh->prepare($query) || $self->dberror($query); + + for ($i = 0; $i <= $#p; $i += 2) { + $sth->execute($p[$i], $p[$i+1], $s{message}); + } + + $sth->finish; + } + + if ($s{print}) { + # formname:format:printer + @p = split /:/, $s{print}; + + $query = qq|INSERT INTO recurringprint (id, formname, format, printer) + VALUES ($self->{id}, ?, ?, ?)|; + + $sth = $dbh->prepare($query) || $self->dberror($query); + + for ($i = 0; $i <= $#p; $i += 3) { + $p = ($p[$i+2]) ? $p[$i+2] : ""; + $sth->execute($p[$i], $p[$i+1], $p); + } + + $sth->finish; + } + } + + if ($disconnect) { + $dbh->commit; + $dbh->disconnect; + } +} + + +sub save_intnotes { + + my ($self, $myconfig, $vc) = @_; + + # no id return + return unless $self->{id}; + + my $dbh = $self->dbconnect($myconfig); + + my $query = qq|UPDATE $vc + SET intnotes = |.$dbh->quote($self->{intnotes}).qq| + WHERE id = $self->{id}|; + + $dbh->do($query) || $self->dberror($query); + $dbh->disconnect; +} + + +sub update_defaults { + + my ($self, $myconfig, $fld, $dbh) = @_; + + my $closedb; + + if (! $dbh) { + $dbh = $self->dbconnect_noauto($myconfig); + $closedb = 1; + } + + my $query = qq|SELECT $fld FROM defaults FOR UPDATE|; + ($_) = $dbh->selectrow_array($query); + + $_ = "0" unless $_; + + # check for and replace + # <%DATE%>, <%YYMMDD%>, <%YEAR%>, <%MONTH%>, <%DAY%> or variations of + # <%NAME 1 1 3%>, <%BUSINESS%>, <%BUSINESS 10%>, <%CURR...%> + # <%DESCRIPTION 1 1 3%>, <%ITEM 1 1 3%>, <%PARTSGROUP 1 1 3%> only for parts + # <%PHONE%> for customer and vendors + + my $num = $_; + $num =~ s/.*?<%.*?%>//g; + ($num) = $num =~ /(\d+)/; + + if (defined $num) { + my $incnum; + # if we have leading zeros check how long it is + + if ($num =~ /^0/) { + my $l = length $num; + $incnum = $num + 1; + $l -= length $incnum; + + # pad it out with zeros + my $padzero = "0" x $l; + $incnum = ("0" x $l) . $incnum; + } else { + $incnum = $num + 1; + } + + s/$num/$incnum/; + } + + my $dbvar = $_; + my $var = $_; + my $str; + my $param; + + if (/<%/) { + + while (/<%/) { + + s/<%.*?%>//; + last unless $&; + $param = $&; + $str = ""; + + if ($param =~ /<%date%>/i) { + $str = ($self->split_date($myconfig->{dateformat}, $self->{transdate}))[0]; + $var =~ s/$param/$str/; + } + + if ($param =~ /<%(name|business|description|item|partsgroup|phone|custom)/i) { + + my $fld = lc $&; + $fld =~ s/<%//; + + if ($fld =~ /name/) { + if ($self->{type}) { + $fld = $self->{vc}; + } + } + + my $p = $param; + $p =~ s/(<|>|%)//g; + my @p = split / /, $p; + my @n = split / /, uc $self->{$fld}; + + if ($#p > 0) { + + for (my $i = 1; $i <= $#p; $i++) { + $str .= substr($n[$i-1], 0, $p[$i]); + } + + } else { + ($str) = split /--/, $self->{$fld}; + } + + $var =~ s/$param/$str/; + $var =~ s/\W//g if $fld eq 'phone'; + } + + if ($param =~ /<%(yy|mm|dd)/i) { + + my $p = $param; + $p =~ s/(<|>|%)//g; + my $spc = $p; + $spc =~ s/\w//g; + $spc = substr($spc, 0, 1); + my %d = ( yy => 1, mm => 2, dd => 3 ); + my @p = (); + + my @a = $self->split_date($myconfig->{dateformat}, $self->{transdate}); + for (sort keys %d) { push @p, $a[$d{$_}] if ($p =~ /$_/) } + $str = join $spc, @p; + $var =~ s/$param/$str/; + } + + if ($param =~ /<%curr/i) { + $var =~ s/$param/$self->{currency}/; + } + } + } + + $query = qq|UPDATE defaults + SET $fld = '$dbvar'|; + + $dbh->do($query) || $form->dberror($query); + + if ($closedb) { + $dbh->commit; + $dbh->disconnect; + } + + $var; +} + + +sub split_date { + + my ($self, $dateformat, $date) = @_; + + my @d = localtime; + my $mm; + my $dd; + my $yy; + my $rv; + + if (! $date) { + $dd = $d[3]; + $mm = ++$d[4]; + $yy = substr($d[5],-2); + $mm = substr("0$mm", -2); + $dd = substr("0$dd", -2); + } + + if ($dateformat =~ /^yy/) { + + if ($date) { + + if ($date =~ /\D/) { + ($yy, $mm, $dd) = split /\D/, $date; + $mm *= 1; + $dd *= 1; + $mm = substr("0$mm", -2); + $dd = substr("0$dd", -2); + $yy = substr($yy, -2); + $rv = "$yy$mm$dd"; + } else { + $rv = $date; + } + } else { + $rv = "$yy$mm$dd"; + } + } + + if ($dateformat =~ /^mm/) { + + if ($date) { + + if ($date =~ /\D/) { + ($mm, $dd, $yy) = split /\D/, $date; + $mm *= 1; + $dd *= 1; + $mm = substr("0$mm", -2); + $dd = substr("0$dd", -2); + $yy = substr($yy, -2); + $rv = "$mm$dd$yy"; + } else { + $rv = $date; + } + } else { + $rv = "$mm$dd$yy"; + } + } + + if ($dateformat =~ /^dd/) { + + if ($date) { + + if ($date =~ /\D/) { + ($dd, $mm, $yy) = split /\D/, $date; + $mm *= 1; + $dd *= 1; + $mm = substr("0$mm", -2); + $dd = substr("0$dd", -2); + $yy = substr($yy, -2); + $rv = "$dd$mm$yy"; + } else { + $rv = $date; + } + } else { + $rv = "$dd$mm$yy"; + } + } + + ($rv, $yy, $mm, $dd); +} + + +sub from_to { + + my ($self, $yy, $mm, $interval) = @_; + + use Time::Local; + + my @t; + my $dd = 1; + my $fromdate = "$yy${mm}01"; + my $bd = 1; + + if (defined $interval) { + + if ($interval == 12) { + $yy++; + } else { + + if (($mm += $interval) > 12) { + $mm -= 12; + $yy++; + } + + if ($interval == 0) { + @t = localtime(time); + $dd = $t[3]; + $mm = $t[4] + 1; + $yy = $t[5] + 1900; + $bd = 0; + } + } + + } else { + + if (++$mm > 12) { + $mm -= 12; + $yy++; + } + } + + $mm--; + @t = localtime(timelocal(0,0,0,$dd,$mm,$yy) - $bd); + + $t[4]++; + $t[4] = substr("0$t[4]",-2); + $t[3] = substr("0$t[3]",-2); + $t[5] += 1900; + + ($fromdate, "$t[5]$t[4]$t[3]"); +} + + +sub audittrail { + + my ($self, $dbh, $myconfig, $audittrail) = @_; + + # table, $reference, $formname, $action, $id, $transdate) = @_; + + my $query; + my $rv; + my $disconnect; + + if (! $dbh) { + $dbh = $self->dbconnect($myconfig); + $disconnect = 1; + } + + # if we have an id add audittrail, otherwise get a new timestamp + + if ($audittrail->{id}) { + + $query = qq|SELECT audittrail FROM defaults|; + + if ($dbh->selectrow_array($query)) { + + my ($null, $employee_id) = $self->get_employee($dbh); + + if ($self->{audittrail} && !$myconfig) { + + chop $self->{audittrail}; + + my @a = split /\|/, $self->{audittrail}; + my %newtrail = (); + my $key; + my $i; + my @flds = qw(tablename reference formname action transdate); + + # put into hash and remove dups + while (@a) { + $key = "$a[2]$a[3]"; + $i = 0; + $newtrail{$key} = { map { $_ => $a[$i++] } @flds }; + splice @a, 0, 5; + } + + $query = qq|INSERT INTO audittrail (trans_id, tablename, reference, + formname, action, employee_id, transdate) + VALUES ($audittrail->{id}, ?, ?, ?, ?, $employee_id, ?)|; + + my $sth = $dbh->prepare($query) || $self->dberror($query); + + foreach $key (sort { $newtrail{$a}{transdate} cmp $newtrail{$b}{transdate} } keys %newtrail) { + + $i = 1; + for (@flds) { $sth->bind_param($i++, $newtrail{$key}{$_}) } + + $sth->execute || $self->dberror; + $sth->finish; + } + } + + if ($audittrail->{transdate}) { + + $query = qq|INSERT INTO audittrail (trans_id, tablename, reference, + formname, action, employee_id, transdate) + VALUES ($audittrail->{id}, '$audittrail->{tablename}', | + .$dbh->quote($audittrail->{reference}).qq|', + '$audittrail->{formname}', '$audittrail->{action}', + $employee_id, '$audittrail->{transdate}')|; + + } else { + $query = qq|INSERT INTO audittrail (trans_id, tablename, reference, + formname, action, employee_id) + VALUES ($audittrail->{id}, + '$audittrail->{tablename}', | + .$dbh->quote($audittrail->{reference}).qq|, + '$audittrail->{formname}', '$audittrail->{action}', + $employee_id)|; + } + + $dbh->do($query); + } + + } else { + + $query = qq|SELECT current_timestamp FROM defaults|; + my ($timestamp) = $dbh->selectrow_array($query); + + $rv = "$audittrail->{tablename}|$audittrail->{reference}|$audittrail->{formname}|$audittrail->{action}|$timestamp|"; + } + + $dbh->disconnect if $disconnect; + $rv; +} + +package Locale; + +sub new { + my ($type, $country, $NLS_file) = @_; + my $self = {}; + + %self = (); + + if ($country && -d "locale/$country") { + $self->{countrycode} = $country; + eval { require "locale/$country/$NLS_file"; }; + } + + $self->{NLS_file} = $NLS_file; + $self->{charset} = $self{charset}; + + push @{ $self->{LONG_MONTH} }, ("January", "February", "March", "April", "May ", "June", "July", "August", "September", "October", "November", "December"); + push @{ $self->{SHORT_MONTH} }, (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)); + + bless $self, $type; +} + + +sub text { + my ($self, $text) = @_; + return (exists $self{texts}{$text}) ? $self{texts}{$text} : $text; +} + + +sub findsub { + + my ($self, $text) = @_; + + if (exists $self{subs}{$text}) { + $text = $self{subs}{$text}; + } else { + if ($self->{countrycode} && $self->{NLS_file}) { + Form->error("$text not defined in locale/$self->{countrycode}/$self->{NLS_file}"); + } + } + + $text; +} + + +sub date { + + my ($self, $myconfig, $date, $longformat) = @_; + + my $longdate = ""; + my $longmonth = ($longformat) ? 'LONG_MONTH' : 'SHORT_MONTH'; + + + if ($date) { + + # get separator + $spc = $myconfig->{dateformat}; + $spc =~ s/\w//g; + $spc = substr($spc, 0, 1); + + if ($date =~ /\D/) { + + if ($myconfig->{dateformat} =~ /^yy/) { + ($yy, $mm, $dd) = split /\D/, $date; + } + + if ($myconfig->{dateformat} =~ /^mm/) { + ($mm, $dd, $yy) = split /\D/, $date; + } + + if ($myconfig->{dateformat} =~ /^dd/) { + ($dd, $mm, $yy) = split /\D/, $date; + } + + } else { + + $date = substr($date, 2); + ($yy, $mm, $dd) = ($date =~ /(..)(..)(..)/); + } + + $dd *= 1; + $mm--; + $yy += 2000 if length $yy == 2; + + if ($myconfig->{dateformat} =~ /^dd/) { + + $mm++; + $dd = substr("0$dd", -2); + $mm = substr("0$mm", -2); + $longdate = "$dd$spc$mm$spc$yy"; + + if (defined $longformat) { + $longdate = "$dd"; + $longdate .= ($spc eq '.') ? ". " : " "; + $longdate .= &text($self, $self->{$longmonth}[--$mm])." $yy"; + } + + } elsif ($myconfig->{dateformat} =~ /^yy/) { + + $mm++; + $dd = substr("0$dd", -2); + $mm = substr("0$mm", -2); + $longdate = "$yy$spc$mm$spc$dd"; + + if (defined $longformat) { + $longdate = &text($self, $self->{$longmonth}[--$mm])." $dd $yy"; + } + + } else { + + $mm++; + $dd = substr("0$dd", -2); + $mm = substr("0$mm", -2); + $longdate = "$mm$spc$dd$spc$yy"; + + if (defined $longformat) { + $longdate = &text($self, $self->{$longmonth}[--$mm])." $dd $yy"; + } + } + } + + $longdate; +} + +1; diff --git a/LedgerSMB/GL.pm b/LedgerSMB/GL.pm new file mode 100755 index 00000000..7c1d4fdd --- /dev/null +++ b/LedgerSMB/GL.pm @@ -0,0 +1,526 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# +# General ledger backend code +# +#====================================================================== + +package GL; + + +sub delete_transaction { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my %audittrail = ( tablename => 'gl', + reference => $form->{reference}, + formname => 'transaction', + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $query = qq|DELETE FROM gl WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM acc_trans WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # commit and redirect + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; +} + + +sub post_transaction { + + my ($self, $myconfig, $form) = @_; + + my $null; + my $project_id; + my $department_id; + my $i; + + # connect to database, turn off AutoCommit + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + + if ($form->{id}) { + + $query = qq|SELECT id FROM gl WHERE id = $form->{id}|; + ($form->{id}) = $dbh->selectrow_array($query); + + if ($form->{id}) { + # delete individual transactions + $query = qq|DELETE FROM acc_trans + WHERE trans_id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + } + } + + if (! $form->{id}) { + + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO gl (reference, employee_id) + VALUES ('$uid', (SELECT id FROM employee + WHERE login = '$form->{login}'))|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM gl + WHERE reference = '$uid'|; + + ($form->{id}) = $dbh->selectrow_array($query); + } + + ($null, $department_id) = split /--/, $form->{department}; + $department_id *= 1; + + $form->{reference} = $form->update_defaults($myconfig, 'glnumber', $dbh) unless $form->{reference}; + $form->{reference} ||= $form->{id}; + + $query = qq|UPDATE gl + SET reference = |.$dbh->quote($form->{reference}).qq|, + description = |.$dbh->quote($form->{description}).qq|, + notes = |.$dbh->quote($form->{notes}).qq|, + transdate = '$form->{transdate}', + department_id = $department_id + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + my $amount = 0; + my $posted = 0; + my $debit; + my $credit; + + # insert acc_trans transactions + for $i (1 .. $form->{rowcount}) { + + $debit = $form->parse_amount($myconfig, $form->{"debit_$i"}); + $credit = $form->parse_amount($myconfig, $form->{"credit_$i"}); + + # extract accno + ($accno) = split(/--/, $form->{"accno_$i"}); + + if ($credit) { + $amount = $credit; + $posted = 0; + } + + if ($debit) { + $amount = $debit * -1; + $posted = 0; + } + + # add the record + if (! $posted) { + + ($null, $project_id) = split /--/, $form->{"projectnumber_$i"}; + $project_id ||= 'NULL'; + + for (qw(fx_transaction cleared)) { $form->{"${_}_$i"} *= 1 } + + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, source, project_id, + fx_transaction, memo, cleared) + VALUES ($form->{id}, (SELECT id + FROM chart + WHERE accno = '$accno'), + $amount, '$form->{transdate}', |. + $dbh->quote($form->{"source_$i"}) .qq|, + $project_id, '$form->{"fx_transaction_$i"}', |. + $dbh->quote($form->{"memo_$i"}).qq|, + '$form->{"cleared_$i"}')|; + + $dbh->do($query) || $form->dberror($query); + + $posted = 1; + } + } + + my %audittrail = ( tablename => 'gl', + reference => $form->{reference}, + formname => 'transaction', + action => 'posted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + $form->save_recurring($dbh, $myconfig); + + # commit and redirect + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; +} + + + +sub all_transactions { + + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + my $query; + my $sth; + my $var; + my $null; + + my ($glwhere, $arwhere, $apwhere) = ("1 = 1", "1 = 1", "1 = 1"); + + if ($form->{reference} ne "") { + $var = $form->like(lc $form->{reference}); + $glwhere .= " AND lower(g.reference) LIKE '$var'"; + $arwhere .= " AND lower(a.invnumber) LIKE '$var'"; + $apwhere .= " AND lower(a.invnumber) LIKE '$var'"; + } + + if ($form->{department} ne "") { + ($null, $var) = split /--/, $form->{department}; + $glwhere .= " AND g.department_id = $var"; + $arwhere .= " AND a.department_id = $var"; + $apwhere .= " AND a.department_id = $var"; + } + + if ($form->{source} ne "") { + $var = $form->like(lc $form->{source}); + $glwhere .= " AND lower(ac.source) LIKE '$var'"; + $arwhere .= " AND lower(ac.source) LIKE '$var'"; + $apwhere .= " AND lower(ac.source) LIKE '$var'"; + } + + if ($form->{memo} ne "") { + $var = $form->like(lc $form->{memo}); + $glwhere .= " AND lower(ac.memo) LIKE '$var'"; + $arwhere .= " AND lower(ac.memo) LIKE '$var'"; + $apwhere .= " AND lower(ac.memo) LIKE '$var'"; + } + + ($form->{datefrom}, $form->{dateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{datefrom}) { + $glwhere .= " AND ac.transdate >= '$form->{datefrom}'"; + $arwhere .= " AND ac.transdate >= '$form->{datefrom}'"; + $apwhere .= " AND ac.transdate >= '$form->{datefrom}'"; + } + + if ($form->{dateto}) { + $glwhere .= " AND ac.transdate <= '$form->{dateto}'"; + $arwhere .= " AND ac.transdate <= '$form->{dateto}'"; + $apwhere .= " AND ac.transdate <= '$form->{dateto}'"; + } + + if ($form->{amountfrom}) { + $glwhere .= " AND abs(ac.amount) >= $form->{amountfrom}"; + $arwhere .= " AND abs(ac.amount) >= $form->{amountfrom}"; + $apwhere .= " AND abs(ac.amount) >= $form->{amountfrom}"; + } + + if ($form->{amountto}) { + $glwhere .= " AND abs(ac.amount) <= $form->{amountto}"; + $arwhere .= " AND abs(ac.amount) <= $form->{amountto}"; + $apwhere .= " AND abs(ac.amount) <= $form->{amountto}"; + } + + if ($form->{description}) { + + $var = $form->like(lc $form->{description}); + $glwhere .= " AND lower(g.description) LIKE '$var'"; + $arwhere .= " AND (lower(ct.name) LIKE '$var' + OR lower(ac.memo) LIKE '$var' + OR a.id IN (SELECT DISTINCT trans_id + FROM invoice + WHERE lower(description) LIKE '$var'))"; + + $apwhere .= " AND (lower(ct.name) LIKE '$var' + OR lower(ac.memo) LIKE '$var' + OR a.id IN (SELECT DISTINCT trans_id + FROM invoice + WHERE lower(description) LIKE '$var'))"; + } + + if ($form->{notes}) { + $var = $form->like(lc $form->{notes}); + $glwhere .= " AND lower(g.notes) LIKE '$var'"; + $arwhere .= " AND lower(a.notes) LIKE '$var'"; + $apwhere .= " AND lower(a.notes) LIKE '$var'"; + } + + if ($form->{accno}) { + $glwhere .= " AND c.accno = '$form->{accno}'"; + $arwhere .= " AND c.accno = '$form->{accno}'"; + $apwhere .= " AND c.accno = '$form->{accno}'"; + } + + if ($form->{gifi_accno}) { + $glwhere .= " AND c.gifi_accno = '$form->{gifi_accno}'"; + $arwhere .= " AND c.gifi_accno = '$form->{gifi_accno}'"; + $apwhere .= " AND c.gifi_accno = '$form->{gifi_accno}'"; + } + + if ($form->{category} ne 'X') { + $glwhere .= " AND c.category = '$form->{category}'"; + $arwhere .= " AND c.category = '$form->{category}'"; + $apwhere .= " AND c.category = '$form->{category}'"; + } + + if ($form->{accno}) { + + # get category for account + $query = qq|SELECT category, link, contra, description + FROM chart + WHERE accno = '$form->{accno}'|; + + ($form->{category}, $form->{link}, $form->{contra}, + $form->{account_description}) = $dbh->selectrow_array($query); + + if ($form->{datefrom}) { + + $query = qq|SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + WHERE c.accno = '$form->{accno}' + AND ac.transdate < date '$form->{datefrom}' |; + + ($form->{balance}) = $dbh->selectrow_array($query); + } + } + + if ($form->{gifi_accno}) { + + # get category for account + $query = qq|SELECT c.category, c.link, c.contra, g.description + FROM chart c + LEFT JOIN gifi g ON (g.accno = c.gifi_accno) + WHERE c.gifi_accno = '$form->{gifi_accno}'|; + + ($form->{category}, $form->{link}, $form->{contra}, + $form->{gifi_account_description}) = $dbh->selectrow_array($query); + + if ($form->{datefrom}) { + + $query = qq|SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + WHERE c.gifi_accno = '$form->{gifi_accno}' + AND ac.transdate < date '$form->{datefrom}' |; + + ($form->{balance}) = $dbh->selectrow_array($query); + } + } + + my $false = ($myconfig->{dbdriver} =~ /Pg/) ? FALSE : q|'0'|; + + my %ordinal = ( id => 1, + reference => 4, + description => 5, + transdate => 6, + source => 7, + accno => 9, + department => 15, + memo => 16 ); + + my @a = (id, transdate, reference, source, description, accno); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT g.id, 'gl' AS type, $false AS invoice, g.reference, + g.description, ac.transdate, ac.source, + ac.amount, c.accno, c.gifi_accno, g.notes, c.link, + '' AS till, ac.cleared, d.description AS department, + ac.memo + FROM gl AS g + JOIN acc_trans ac ON (g.id = ac.trans_id) + JOIN chart c ON (ac.chart_id = c.id) + LEFT JOIN department d ON (d.id = g.department_id) + WHERE $glwhere + + UNION ALL + + SELECT a.id, 'ar' AS type, a.invoice, a.invnumber, + ct.name, ac.transdate, ac.source, + ac.amount, c.accno, c.gifi_accno, a.notes, c.link, + a.till, ac.cleared, d.description AS department, + ac.memo + FROM ar a + JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart c ON (ac.chart_id = c.id) + JOIN customer ct ON (a.customer_id = ct.id) + LEFT JOIN department d ON (d.id = a.department_id) + WHERE $arwhere + + UNION ALL + + SELECT a.id, 'ap' AS type, a.invoice, a.invnumber, + ct.name, ac.transdate, ac.source, + ac.amount, c.accno, c.gifi_accno, a.notes, c.link, + a.till, ac.cleared, d.description AS department, + ac.memo + FROM ap a + JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart c ON (ac.chart_id = c.id) + JOIN vendor ct ON (a.vendor_id = ct.id) + LEFT JOIN department d ON (d.id = a.department_id) + WHERE $apwhere + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + # gl + if ($ref->{type} eq "gl") { + $ref->{module} = "gl"; + } + + # ap + if ($ref->{type} eq "ap") { + + if ($ref->{invoice}) { + $ref->{module} = "ir"; + } else { + $ref->{module} = "ap"; + } + } + + # ar + if ($ref->{type} eq "ar") { + + if ($ref->{invoice}) { + $ref->{module} = ($ref->{till}) ? "ps" : "is"; + } else { + $ref->{module} = "ar"; + } + } + + if ($ref->{amount} < 0) { + $ref->{debit} = $ref->{amount} * -1; + $ref->{credit} = 0; + } else { + $ref->{credit} = $ref->{amount}; + $ref->{debit} = 0; + } + + push @{ $form->{GL} }, $ref; + } + + $sth->finish; + $dbh->disconnect; +} + + +sub transaction { + + my ($self, $myconfig, $form) = @_; + + my ($query, $sth, $ref); + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + if ($form->{id}) { + + $query = "SELECT closedto, revtrans + FROM defaults"; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{closedto}, $form->{revtrans}) = $sth->fetchrow_array; + $sth->finish; + + $query = qq|SELECT g.*, d.description AS department + FROM gl g + LEFT JOIN department d ON (d.id = g.department_id) + WHERE g.id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # retrieve individual rows + $query = qq|SELECT ac.*, c.accno, c.description, p.projectnumber + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + LEFT JOIN project p ON (p.id = ac.project_id) + WHERE ac.trans_id = $form->{id} + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + if ($ref->{fx_transaction}) { + $form->{transfer} = 1; + } + push @{ $form->{GL} }, $ref; + } + + # get recurring transaction + $form->get_recurring($dbh); + + } else { + $query = "SELECT current_date AS transdate, closedto, revtrans + FROM defaults"; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{transdate}, $form->{closedto}, $form->{revtrans}) = $sth->fetchrow_array; + } + + $sth->finish; + + # get chart of accounts + $query = qq|SELECT accno,description + FROM chart + WHERE charttype = 'A' + ORDER BY accno|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_accno} }, $ref; + } + + $sth->finish; + + # get departments + $form->all_departments($myconfig, $dbh); + + # get projects + $form->all_projects($myconfig, $dbh, $form->{transdate}); + + $dbh->disconnect; + +} + +1; diff --git a/LedgerSMB/HR.pm b/LedgerSMB/HR.pm new file mode 100755 index 00000000..5ff8c253 --- /dev/null +++ b/LedgerSMB/HR.pm @@ -0,0 +1,555 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# backend code for human resources and payroll +# +#====================================================================== + +package HR; + + +sub get_employee { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $sth; + my $ref; + my $notid = ""; + + if ($form->{id}) { + $query = qq|SELECT e.* + FROM employee e + WHERE e.id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + + # check if employee can be deleted, orphaned + $form->{status} = "orphaned" unless $ref->{login}; + +$form->{status} = 'orphaned'; # leave orphaned for now until payroll is done + + $ref->{employeelogin} = $ref->{login}; + delete $ref->{login}; + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + + # get manager + $form->{managerid} *= 1; + $query = qq|SELECT name + FROM employee + WHERE id = $form->{managerid}|; + ($form->{manager}) = $dbh->selectrow_array($query); + + +######### disabled for now +if ($form->{deductions}) { + # get allowances + $query = qq|SELECT d.id, d.description, da.before, da.after, da.rate + FROM employeededuction da + JOIN deduction d ON (da.deduction_id = d.id) + WHERE da.employee_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{rate} *= 100; + push @{ $form->{all_employeededuction} }, $ref; + } + $sth->finish; +} + + $notid = qq|AND id != $form->{id}|; + + } else { + + $query = qq|SELECT current_date FROM defaults|; + ($form->{startdate}) = $dbh->selectrow_array($query); + + } + + # get managers + $query = qq|SELECT id, name + FROM employee + WHERE sales = '1' + AND role = 'manager' + $notid + ORDER BY 2|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_manager} }, $ref; + } + $sth->finish; + + + # get deductions +if ($form->{deductions}) { + $query = qq|SELECT id, description + FROM deduction + ORDER BY 2|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_deduction} }, $ref; + } + $sth->finish; +} + + $dbh->disconnect; + +} + + + +sub save_employee { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + my $query; + my $sth; + + if (! $form->{id}) { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO employee (name) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM employee + WHERE name = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{id}) = $sth->fetchrow_array; + $sth->finish; + } + + my ($null, $managerid) = split /--/, $form->{manager}; + $managerid *= 1; + $form->{sales} *= 1; + + $form->{employeenumber} = $form->update_defaults($myconfig, "employeenumber", $dbh) if ! $form->{employeenumber}; + + $query = qq|UPDATE employee SET + employeenumber = |.$dbh->quote($form->{employeenumber}).qq|, + name = |.$dbh->quote($form->{name}).qq|, + address1 = |.$dbh->quote($form->{address1}).qq|, + address2 = |.$dbh->quote($form->{address2}).qq|, + city = |.$dbh->quote($form->{city}).qq|, + state = |.$dbh->quote($form->{state}).qq|, + zipcode = |.$dbh->quote($form->{zipcode}).qq|, + country = |.$dbh->quote($form->{country}).qq|, + workphone = '$form->{workphone}', + homephone = '$form->{homephone}', + startdate = |.$form->dbquote($form->{startdate}, SQL_DATE).qq|, + enddate = |.$form->dbquote($form->{enddate}, SQL_DATE).qq|, + notes = |.$dbh->quote($form->{notes}).qq|, + role = '$form->{role}', + sales = '$form->{sales}', + email = |.$dbh->quote($form->{email}).qq|, + ssn = '$form->{ssn}', + dob = |.$form->dbquote($form->{dob}, SQL_DATE).qq|, + iban = '$form->{iban}', + bic = '$form->{bic}', + managerid = $managerid + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + +# for now +if ($form->{selectdeduction}) { + # insert deduction and allowances for payroll + $query = qq|DELETE FROM employeededuction + WHERE employee_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO employeededuction (employee_id, deduction_id, + before, after, rate) VALUES ($form->{id},?,?,?,?)|; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + for ($i = 1; $i <= $form->{deduction_rows}; $i++) { + for (qw(before after)) { $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } + ($null, $deduction_id) = split /--/, $form->{"deduction_$i"}; + if ($deduction_id) { + $sth->execute($deduction_id, $form->{"before_$i"}, $form->{"after_$i"}, $form->{"rate_$i"} / 100) || $form->dberror($query); + } + } + $sth->finish; +} + + $dbh->commit; + $dbh->disconnect; + +} + + +sub delete_employee { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + # delete employee + my $query = qq|DELETE FROM $form->{db} + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $dbh->commit; + $dbh->disconnect; + +} + + +sub employees { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $where = "1 = 1"; + $form->{sort} = ($form->{sort}) ? $form->{sort} : "name"; + my @a = qw(name); + my $sortorder = $form->sort_order(\@a); + + my $var; + + if ($form->{startdatefrom}) { + $where .= " AND e.startdate >= '$form->{startdatefrom}'"; + } + if ($form->{startdateto}) { + $where .= " AND e.startddate <= '$form->{startdateto}'"; + } + if ($form->{name} ne "") { + $var = $form->like(lc $form->{name}); + $where .= " AND lower(e.name) LIKE '$var'"; + } + if ($form->{notes} ne "") { + $var = $form->like(lc $form->{notes}); + $where .= " AND lower(e.notes) LIKE '$var'"; + } + if ($form->{sales} eq 'Y') { + $where .= " AND e.sales = '1'"; + } + if ($form->{status} eq 'orphaned') { + $where .= qq| AND e.login IS NULL|; + } + if ($form->{status} eq 'active') { + $where .= qq| AND e.enddate IS NULL|; + } + if ($form->{status} eq 'inactive') { + $where .= qq| AND e.enddate <= current_date|; + } + + my $query = qq|SELECT e.*, m.name AS manager + FROM employee e + LEFT JOIN employee m ON (m.id = e.managerid) + WHERE $where + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{address} = ""; + for (qw(address1 address2 city state zipcode country)) { $ref->{address} .= "$ref->{$_} " } + push @{ $form->{all_employee} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub get_deduction { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + my $query; + my $sth; + my $ref; + my $item; + my $i; + + if ($form->{id}) { + $query = qq|SELECT d.*, + c1.accno AS ap_accno, + c1.description AS ap_description, + c2.accno AS expense_accno, + c2.description AS expense_description + FROM deduction d + LEFT JOIN chart c1 ON (c1.id = d.ap_accno_id) + LEFT JOIN chart c2 ON (c2.id = d.expense_accno_id) + WHERE d.id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + + # check if orphaned +$form->{status} = 'orphaned'; # for now + + + # get the rates + $query = qq|SELECT rate, amount, above, below + FROM deductionrate + WHERE trans_id = $form->{id} + ORDER BY rate, amount|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{deductionrate} }, $ref; + } + $sth->finish; + + # get all for deductionbase + $query = qq|SELECT d.description, d.id, db.maximum + FROM deductionbase db + JOIN deduction d ON (d.id = db.deduction_id) + WHERE db.trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{deductionbase} }, $ref; + } + $sth->finish; + + # get all for deductionafter + $query = qq|SELECT d.description, d.id + FROM deductionafter da + JOIN deduction d ON (d.id = da.deduction_id) + WHERE da.trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{deductionafter} }, $ref; + } + $sth->finish; + + # build selection list for base and after + $query = qq|SELECT id, description + FROM deduction + WHERE id != $form->{id} + ORDER BY 2|; + + } else { + # build selection list for base and after + $query = qq|SELECT id, description + FROM deduction + ORDER BY 2|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_deduction} }, $ref; + } + $sth->finish; + + + my %category = ( ap => 'L', + expense => 'E' ); + + foreach $item (keys %category) { + $query = qq|SELECT accno, description + FROM chart + WHERE charttype = 'A' + AND category = '$category{$item}' + ORDER BY accno|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{"${item}_accounts"} }, $ref; + } + $sth->finish; + } + + + $dbh->disconnect; + +} + + +sub deductions { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT d.id, d.description, d.employeepays, d.employerpays, + c1.accno AS ap_accno, c2.accno AS expense_accno, + dr.rate, dr.amount, dr.above, dr.below + FROM deduction d + JOIN deductionrate dr ON (dr.trans_id = d.id) + LEFT JOIN chart c1 ON (d.ap_accno_id = c1.id) + LEFT JOIN chart c2 ON (d.expense_accno_id = c2.id) + ORDER BY 2, 7, 8|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_deduction} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub save_deduction { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + ($form->{ap_accno}) = split /--/, $form->{ap_accno}; + ($form->{expense_accno}) = split /--/, $form->{expense_accno}; + + my $null; + my $deduction_id; + my $query; + my $sth; + + if (! $form->{id}) { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO deduction (description) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM deduction + WHERE description = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{id}) = $sth->fetchrow_array; + $sth->finish; + } + + + for (qw(employeepays employerpays)) { $form->{$_} = $form->parse_amount($myconfig, $form->{$_}) } + + $query = qq|UPDATE deduction SET + description = |.$dbh->quote($form->{description}).qq|, + ap_accno_id = + (SELECT id FROM chart + WHERE accno = '$form->{ap_accno}'), + expense_accno_id = + (SELECT id FROM chart + WHERE accno = '$form->{expense_accno}'), + employerpays = '$form->{employerpays}', + employeepays = '$form->{employeepays}', + fromage = |.$form->dbquote($form->{fromage}, SQL_INT).qq|, + toage = |.$form->dbquote($form->{toage}, SQL_INT).qq| + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + + $query = qq|DELETE FROM deductionrate + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO deductionrate + (trans_id, rate, amount, above, below) VALUES (?,?,?,?,?)|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + for ($i = 1; $i <= $form->{rate_rows}; $i++) { + for (qw(rate amount above below)) { $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } + $form->{"rate_$i"} /= 100; + + if ($form->{"rate_$i"} || $form->{"amount_$i"}) { + $sth->execute($form->{id}, $form->{"rate_$i"}, $form->{"amount_$i"}, $form->{"above_$i"}, $form->{"below_$i"}) || $form->dberror($query); + } + } + $sth->finish; + + + $query = qq|DELETE FROM deductionbase + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO deductionbase + (trans_id, deduction_id, maximum) VALUES (?,?,?)|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + for ($i = 1; $i <= $form->{base_rows}; $i++) { + ($null, $deduction_id) = split /--/, $form->{"base_$i"}; + $form->{"maximum_$i"} = $form->parse_amount($myconfig, $form->{"maximum_$i"}); + if ($deduction_id) { + $sth->execute($form->{id}, $deduction_id, $form->{"maximum_$i"}) || $form->dberror($query); + } + } + $sth->finish; + + + $query = qq|DELETE FROM deductionafter + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO deductionafter + (trans_id, deduction_id) VALUES (?,?)|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + for ($i = 1; $i <= $form->{after_rows}; $i++) { + ($null, $deduction_id) = split /--/, $form->{"after_$i"}; + if ($deduction_id) { + $sth->execute($form->{id}, $deduction_id) || $form->dberror($query); + } + } + $sth->finish; + + $dbh->commit; + $dbh->disconnect; + +} + + +sub delete_deduction { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + # delete deduction + my $query = qq|DELETE FROM $form->{db} + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + foreach $item (qw(rate base after)) { + $query = qq|DELETE FROM deduction$item + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + $dbh->commit; + $dbh->disconnect; + +} + +1; + diff --git a/LedgerSMB/IC.pm b/LedgerSMB/IC.pm new file mode 100755 index 00000000..52ad9654 --- /dev/null +++ b/LedgerSMB/IC.pm @@ -0,0 +1,1714 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Inventory Control backend +# +#====================================================================== + +package IC; + + +sub get_part { + my ($self, $myconfig, $form) = @_; + + # connect to db + my $dbh = $form->dbconnect($myconfig); + my $i; + + my $query = qq|SELECT p.*, + c1.accno AS inventory_accno, c1.description AS inventory_description, + c2.accno AS income_accno, c2.description AS income_description, + c3.accno AS expense_accno, c3.description AS expense_description, + pg.partsgroup + FROM parts p + LEFT JOIN chart c1 ON (p.inventory_accno_id = c1.id) + LEFT JOIN chart c2 ON (p.income_accno_id = c2.id) + LEFT JOIN chart c3 ON (p.expense_accno_id = c3.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + WHERE p.id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + my $ref = $sth->fetchrow_hashref(NAME_lc); + + # copy to $form variables + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + my %oid = ('Pg' => 'a.oid', + 'PgPP' => 'a.oid', + 'Oracle' => 'a.rowid', + 'DB2' => '1=1' + ); + + # part, service item or labor + $form->{item} = ($form->{inventory_accno_id}) ? 'part' : 'service'; + $form->{item} = 'labor' if ! $form->{income_accno_id}; + + if ($form->{assembly}) { + $form->{item} = 'assembly'; + + # retrieve assembly items + $query = qq|SELECT p.id, p.partnumber, p.description, + p.sellprice, p.weight, a.qty, a.bom, a.adj, p.unit, + p.lastcost, p.listprice, + pg.partsgroup, p.assembly, p.partsgroup_id + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + WHERE a.id = $form->{id} + ORDER BY $oid{$myconfig->{dbdriver}}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{assembly_rows} = 0; + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{assembly_rows}++; + foreach my $key ( keys %{ $ref } ) { + $form->{"${key}_$form->{assembly_rows}"} = $ref->{$key}; + } + } + $sth->finish; + + } + + # setup accno hash for <option checked> {amount} is used in create_links + for (qw(inventory income expense)) { $form->{amount}{"IC_$_"} = { accno => $form->{"${_}_accno"}, description => $form->{"${_}_description"} } } + + + if ($form->{item} =~ /(part|assembly)/) { + # get makes + if ($form->{makemodel} ne "") { + $query = qq|SELECT make, model + FROM makemodel + WHERE parts_id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{makemodels} }, $ref; + } + $sth->finish; + } + } + + # now get accno for taxes + $query = qq|SELECT c.accno + FROM chart c, partstax pt + WHERE pt.chart_id = c.id + AND pt.parts_id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (($key) = $sth->fetchrow_array) { + $form->{amount}{$key} = $key; + } + + $sth->finish; + + # is it an orphan + $query = qq|SELECT parts_id + FROM invoice + WHERE parts_id = $form->{id} + UNION + SELECT parts_id + FROM orderitems + WHERE parts_id = $form->{id} + UNION + SELECT parts_id + FROM assembly + WHERE parts_id = $form->{id} + UNION + SELECT parts_id + FROM jcitems + WHERE parts_id = $form->{id}|; + ($form->{orphaned}) = $dbh->selectrow_array($query); + $form->{orphaned} = !$form->{orphaned}; + + $form->{orphaned} = 0 if $form->{project_id}; + + if ($form->{item} eq 'assembly') { + if ($form->{orphaned}) { + $form->{orphaned} = !$form->{onhand}; + } + } + + if ($form->{item} =~ /(part|service)/) { + # get vendors + $query = qq|SELECT v.id, v.name, pv.partnumber, + pv.lastcost, pv.leadtime, pv.curr AS vendorcurr + FROM partsvendor pv + JOIN vendor v ON (v.id = pv.vendor_id) + WHERE pv.parts_id = $form->{id} + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{vendormatrix} }, $ref; + } + $sth->finish; + } + + # get matrix + if ($form->{item} ne 'labor') { + $query = qq|SELECT pc.pricebreak, pc.sellprice AS customerprice, + pc.curr AS customercurr, + pc.validfrom, pc.validto, + c.name, c.id AS cid, g.pricegroup, g.id AS gid + FROM partscustomer pc + LEFT JOIN customer c ON (c.id = pc.customer_id) + LEFT JOIN pricegroup g ON (g.id = pc.pricegroup_id) + WHERE pc.parts_id = $form->{id} + ORDER BY c.name, g.pricegroup, pc.pricebreak|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{customermatrix} }, $ref; + } + $sth->finish; + } + + $dbh->disconnect; + +} + + +sub save { + my ($self, $myconfig, $form) = @_; + + ($form->{inventory_accno}) = split(/--/, $form->{IC_inventory}); + ($form->{expense_accno}) = split(/--/, $form->{IC_expense}); + ($form->{income_accno}) = split(/--/, $form->{IC_income}); + + # connect to database, turn off AutoCommit + my $dbh = $form->dbconnect_noauto($myconfig); + + # save the part + # make up a unique handle and store in partnumber field + # then retrieve the record based on the unique handle to get the id + # replace the partnumber field with the actual variable + # add records for makemodel + + # if there is a $form->{id} then replace the old entry + # delete all makemodel entries and add the new ones + + # undo amount formatting + for (qw(rop weight listprice sellprice lastcost stock)) { $form->{$_} = $form->parse_amount($myconfig, $form->{$_}) } + + $form->{makemodel} = (($form->{make_1}) || ($form->{model_1})) ? 1 : 0; + + $form->{assembly} = ($form->{item} eq 'assembly') ? 1 : 0; + for (qw(alternate obsolete onhand)) { $form->{$_} *= 1 } + + my $query; + my $sth; + my $i; + my $null; + my $vendor_id; + my $customer_id; + + if ($form->{id}) { + + # get old price + $query = qq|SELECT id, listprice, sellprice, lastcost, weight, project_id + FROM parts + WHERE id = $form->{id}|; + my ($id, $listprice, $sellprice, $lastcost, $weight, $project_id) = $dbh->selectrow_array($query); + + if ($id) { + + if (!$project_id) { + # if item is part of an assembly adjust all assemblies + $query = qq|SELECT id, qty, adj + FROM assembly + WHERE parts_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + while (my ($id, $qty, $adj) = $sth->fetchrow_array) { + &update_assembly($dbh, $form, $id, $qty, $adj, $listprice * 1, $sellprice * 1, $lastcost * 1, $weight * 1); + } + $sth->finish; + } + + if ($form->{item} =~ /(part|service)/) { + # delete partsvendor records + $query = qq|DELETE FROM partsvendor + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + if ($form->{item} !~ /(service|labor)/) { + # delete makemodel records + $query = qq|DELETE FROM makemodel + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + if ($form->{item} eq 'assembly') { + + if ($form->{onhand}) { + &adjust_inventory($dbh, $form, $form->{id}, $form->{onhand} * -1); + } + + if ($form->{orphaned}) { + # delete assembly records + $query = qq|DELETE FROM assembly + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } else { + + for $i (1 .. $form->{assembly_rows} - 1) { + # update BOM, A only + for (qw(bom adj)) { $form->{"${_}_$i"} *= 1 } + + $query = qq|UPDATE assembly SET + bom = '$form->{"bom_$i"}', + adj = '$form->{"adj_$i"}' + WHERE id = $form->{id} + AND parts_id = $form->{"id_$i"}|; + $dbh->do($query) || $form->dberror($query); + } + } + + $form->{onhand} += $form->{stock}; + + } + + # delete tax records + $query = qq|DELETE FROM partstax + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete matrix + $query = qq|DELETE FROM partscustomer + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + } else { + $query = qq|INSERT INTO parts (id) + VALUES ($form->{id})|; + $dbh->do($query) || $form->dberror($query); + } + + } + + + if (!$form->{id}) { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO parts (partnumber) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM parts + WHERE partnumber = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + ($form->{id}) = $sth->fetchrow_array; + $sth->finish; + + $form->{orphaned} = 1; + $form->{onhand} = ($form->{stock} * 1) if $form->{item} eq 'assembly'; + + } + + my $partsgroup_id; + ($null, $partsgroup_id) = split /--/, $form->{partsgroup}; + $partsgroup_id *= 1; + + $form->{partnumber} = $form->update_defaults($myconfig, "partnumber", $dbh) if ! $form->{partnumber}; + + $query = qq|UPDATE parts SET + partnumber = |.$dbh->quote($form->{partnumber}).qq|, + description = |.$dbh->quote($form->{description}).qq|, + makemodel = '$form->{makemodel}', + alternate = '$form->{alternate}', + assembly = '$form->{assembly}', + listprice = $form->{listprice}, + sellprice = $form->{sellprice}, + lastcost = $form->{lastcost}, + weight = $form->{weight}, + priceupdate = |.$form->dbquote($form->{priceupdate}, SQL_DATE).qq|, + unit = |.$dbh->quote($form->{unit}).qq|, + notes = |.$dbh->quote($form->{notes}).qq|, + rop = $form->{rop}, + bin = |.$dbh->quote($form->{bin}).qq|, + inventory_accno_id = (SELECT id FROM chart + WHERE accno = '$form->{inventory_accno}'), + income_accno_id = (SELECT id FROM chart + WHERE accno = '$form->{income_accno}'), + expense_accno_id = (SELECT id FROM chart + WHERE accno = '$form->{expense_accno}'), + obsolete = '$form->{obsolete}', + image = '$form->{image}', + drawing = '$form->{drawing}', + microfiche = '$form->{microfiche}', + partsgroup_id = $partsgroup_id + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + + # insert makemodel records + if ($form->{item} =~ /(part|assembly)/) { + for $i (1 .. $form->{makemodel_rows}) { + if (($form->{"make_$i"} ne "") || ($form->{"model_$i"} ne "")) { + $query = qq|INSERT INTO makemodel (parts_id, make, model) + VALUES ($form->{id},| + .$dbh->quote($form->{"make_$i"}).qq|, | + .$dbh->quote($form->{"model_$i"}).qq|)|; + $dbh->do($query) || $form->dberror($query); + } + } + } + + + # insert taxes + for (split / /, $form->{taxaccounts}) { + if ($form->{"IC_tax_$_"}) { + $query = qq|INSERT INTO partstax (parts_id, chart_id) + VALUES ($form->{id}, + (SELECT id + FROM chart + WHERE accno = '$_'))|; + $dbh->do($query) || $form->dberror($query); + } + } + + + @a = localtime; + $a[5] += 1900; + $a[4]++; + $a[4] = substr("0$a[4]", -2); + $a[3] = substr("0$a[3]", -2); + my $shippingdate = "$a[5]$a[4]$a[3]"; + + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + + # add assembly records + if ($form->{item} eq 'assembly' && !$project_id) { + + if ($form->{orphaned}) { + for $i (1 .. $form->{assembly_rows}) { + $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}); + + if ($form->{"qty_$i"}) { + for (qw(bom adj)) { $form->{"${_}_$i"} *= 1 } + $query = qq|INSERT INTO assembly (id, parts_id, qty, bom, adj) + VALUES ($form->{id}, $form->{"id_$i"}, + $form->{"qty_$i"}, '$form->{"bom_$i"}', + '$form->{"adj_$i"}')|; + $dbh->do($query) || $form->dberror($query); + } + } + } + + # adjust onhand for the parts + if ($form->{onhand}) { + &adjust_inventory($dbh, $form, $form->{id}, $form->{onhand}); + } + + } + + # add vendors + if ($form->{item} ne 'assembly') { + $updparts{$form->{id}} = 1; + + for $i (1 .. $form->{vendor_rows}) { + if (($form->{"vendor_$i"} ne "") && $form->{"lastcost_$i"}) { + + ($null, $vendor_id) = split /--/, $form->{"vendor_$i"}; + + for (qw(lastcost leadtime)) { $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"})} + + $query = qq|INSERT INTO partsvendor (vendor_id, parts_id, partnumber, + lastcost, leadtime, curr) + VALUES ($vendor_id, $form->{id},| + .$dbh->quote($form->{"partnumber_$i"}).qq|, + $form->{"lastcost_$i"}, + $form->{"leadtime_$i"}, '$form->{"vendorcurr_$i"}')|; + $dbh->do($query) || $form->dberror($query); + } + } + } + + + # add pricematrix + for $i (1 .. $form->{customer_rows}) { + + for (qw(pricebreak customerprice)) { $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"})} + + if ($form->{"customerprice_$i"}) { + + ($null, $customer_id) = split /--/, $form->{"customer_$i"}; + $customer_id *= 1; + + ($null, $pricegroup_id) = split /--/, $form->{"pricegroup_$i"}; + $pricegroup_id *= 1; + + $query = qq|INSERT INTO partscustomer (parts_id, customer_id, + pricegroup_id, pricebreak, sellprice, curr, + validfrom, validto) + VALUES ($form->{id}, $customer_id, + $pricegroup_id, $form->{"pricebreak_$i"}, + $form->{"customerprice_$i"}, '$form->{"customercurr_$i"}',| + .$form->dbquote($form->{"validfrom_$i"}, SQL_DATE).qq|, | + .$form->dbquote($form->{"validto_$i"}, SQL_DATE).qq|)|; + $dbh->do($query) || $form->dberror($query); + } + } + + # commit + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + + +sub update_assembly { + my ($dbh, $form, $id, $qty, $adj, $listprice, $sellprice, $lastcost, $weight) = @_; + + my $formlistprice = $form->{listprice}; + my $formsellprice = $form->{sellprice}; + + if (!$adj) { + $formlistprice = $listprice; + $formsellprice = $sellprice; + } + + my $query = qq|SELECT id, qty, adj + FROM assembly + WHERE parts_id = $id|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{$id} = 1; + + while (my ($pid, $aqty, $aadj) = $sth->fetchrow_array) { + &update_assembly($dbh, $form, $pid, $aqty * $qty, $aadj, $listprice, $sellprice, $lastcost, $weight) if !$form->{$pid}; + } + $sth->finish; + + $query = qq|UPDATE parts + SET listprice = listprice + + $qty * ($formlistprice - $listprice), + sellprice = sellprice + + $qty * ($formsellprice - $sellprice), + lastcost = lastcost + + $qty * ($form->{lastcost} - $lastcost), + weight = weight + + $qty * ($form->{weight} - $weight) + WHERE id = $id|; + $dbh->do($query) || $form->dberror($query); + + delete $form->{$id}; + +} + + + +sub retrieve_assemblies { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $where = '1 = 1'; + + if ($form->{partnumber} ne "") { + my $partnumber = $form->like(lc $form->{partnumber}); + $where .= " AND lower(p.partnumber) LIKE '$partnumber'"; + } + + if ($form->{description} ne "") { + my $description = $form->like(lc $form->{description}); + $where .= " AND lower(p.description) LIKE '$description'"; + } + $where .= qq| AND p.obsolete = '0' + AND p.project_id IS NULL|; + + my %ordinal = ( 'partnumber' => 2, + 'description' => 3, + 'bin' => 4 + ); + + my @a = qw(partnumber description bin); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + + # retrieve assembly items + my $query = qq|SELECT p.id, p.partnumber, p.description, + p.bin, p.onhand, p.rop + FROM parts p + WHERE $where + AND p.assembly = '1' + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|SELECT sum(p.inventory_accno_id), p.assembly + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + WHERE a.id = ? + GROUP BY p.assembly|; + my $svh = $dbh->prepare($query) || $form->dberror($query); + + my $inh; + if ($form->{checkinventory}) { + $query = qq|SELECT p.id, p.onhand, a.qty + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + WHERE (p.inventory_accno_id > 0 OR p.assembly) + AND p.income_accno_id > 0 + AND a.id = ?|; + $inh = $dbh->prepare($query) || $form->dberror($query); + } + + my %available = (); + my %required; + my $ref; + my $aref; + my $stock; + my $howmany; + my $ok; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $svh->execute($ref->{id}); + ($ref->{inventory}, $ref->{assembly}) = $svh->fetchrow_array; + $svh->finish; + + if ($ref->{inventory} || $ref->{assembly}) { + $ok = 1; + if ($form->{checkinventory}) { + $inh->execute($ref->{id}) || $form->dberror($query);; + $ok = 0; + %required = (); + + while ($aref = $inh->fetchrow_hashref(NAME_lc)) { + $available{$aref->{id}} = (exists $available{$aref->{id}}) ? $available{$aref->{id}} : $aref->{onhand}; + $required{$aref->{id}} = $aref->{qty}; + + if ($available{$aref->{id}} >= $aref->{qty}) { + + $howmany = ($aref->{qty}) ? int $available{$aref->{id}}/$aref->{qty} : 1; + if ($stock) { + $stock = ($stock > $howmany) ? $howmany : $stock; + } else { + $stock = $howmany; + } + $ok = 1; + + $available{$aref->{id}} -= $aref->{qty} * $stock; + + } else { + $ok = 0; + for (keys %required) { $available{$_} += $required{$_} * $stock } + $stock = 0; + last; + } + } + $inh->finish; + $ref->{stock} = $stock; + + } + push @{ $form->{assembly_items} }, $ref if $ok; + } + } + $sth->finish; + + $dbh->disconnect; + +} + + +sub restock_assemblies { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + for my $i (1 .. $form->{rowcount}) { + + $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}); + + if ($form->{"qty_$i"}) { + &adjust_inventory($dbh, $form, $form->{"id_$i"}, $form->{"qty_$i"}); + } + + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub adjust_inventory { + my ($dbh, $form, $id, $qty) = @_; + + my $query = qq|SELECT p.id, p.inventory_accno_id, p.assembly, a.qty + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + WHERE a.id = $id|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + # is it a service item then loop + if (! $ref->{inventory_accno_id}) { + next if ! $ref->{assembly}; # assembly + } + + # adjust parts onhand + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $ref->{id}|, + $qty * $ref->{qty} * -1); + } + + $sth->finish; + + # update assembly + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $id|, + $qty); + +} + + +sub delete { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off AutoCommit + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + + $query = qq|DELETE FROM parts + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM partstax + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + + if ($form->{item} ne 'assembly') { + $query = qq|DELETE FROM partsvendor + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + # check if it is a part, assembly or service + if ($form->{item} ne 'service') { + $query = qq|DELETE FROM makemodel + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + if ($form->{item} eq 'assembly') { + $query = qq|DELETE FROM assembly + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + if ($form->{item} eq 'alternate') { + $query = qq|DELETE FROM alternate + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + } + + $query = qq|DELETE FROM inventory + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM partscustomer + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM translation + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # commit + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub assembly_item { + my ($self, $myconfig, $form) = @_; + + my $i = $form->{assembly_rows}; + my $var; + my $null; + my $where = "p.obsolete = '0'"; + + if ($form->{"partnumber_$i"} ne "") { + $var = $form->like(lc $form->{"partnumber_$i"}); + $where .= " AND lower(p.partnumber) LIKE '$var'"; + } + if ($form->{"description_$i"} ne "") { + $var = $form->like(lc $form->{"description_$i"}); + $where .= " AND lower(p.description) LIKE '$var'"; + } + if ($form->{"partsgroup_$i"} ne "") { + ($null, $var) = split /--/, $form->{"partsgroup_$i"}; + $where .= qq| AND p.partsgroup_id = $var|; + } + + if ($form->{id}) { + $where .= " AND p.id != $form->{id}"; + } + + if ($form->{"description_$i"} ne "") { + $where .= " ORDER BY p.description"; + } else { + $where .= " ORDER BY p.partnumber"; + } + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, + p.weight, p.onhand, p.unit, p.lastcost, + pg.partsgroup, p.partsgroup_id + FROM parts p + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + WHERE $where|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{item_list} }, $ref; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub all_parts { + my ($self, $myconfig, $form) = @_; + + my $where = '1 = 1'; + my $null; + my $var; + my $ref; + + for (qw(partnumber drawing microfiche)) { + if ($form->{$_} ne "") { + $var = $form->like(lc $form->{$_}); + $where .= " AND lower(p.$_) LIKE '$var'"; + } + } + # special case for description + if ($form->{description} ne "") { + unless ($form->{bought} || $form->{sold} || $form->{onorder} || $form->{ordered} || $form->{rfq} || $form->{quoted}) { + $var = $form->like(lc $form->{description}); + $where .= " AND lower(p.description) LIKE '$var'"; + } + } + + # assembly components + my $assemblyflds; + if ($form->{searchitems} eq 'component') { + $assemblyflds = qq|, p1.partnumber AS assemblypartnumber, a.id AS assembly_id|; + } + + # special case for serialnumber + if ($form->{l_serialnumber}) { + if ($form->{serialnumber} ne "") { + $var = $form->like(lc $form->{serialnumber}); + $where .= " AND lower(i.serialnumber) LIKE '$var'"; + } + } + + if (($form->{warehouse} ne "") || $form->{l_warehouse}) { + $form->{l_warehouse} = 1; + } + + if ($form->{searchitems} eq 'part') { + $where .= " AND p.inventory_accno_id > 0 AND p.income_accno_id > 0"; + } + if ($form->{searchitems} eq 'assembly') { + $form->{bought} = ""; + $where .= " AND p.assembly = '1'"; + } + if ($form->{searchitems} eq 'service') { + $where .= " AND p.assembly = '0' AND p.inventory_accno_id IS NULL"; + } + if ($form->{searchitems} eq 'labor') { + $where .= " AND p.inventory_accno_id > 0 AND p.income_accno_id IS NULL"; + } + + # items which were never bought, sold or on an order + if ($form->{itemstatus} eq 'orphaned') { + $where .= qq| AND p.onhand = 0 + AND p.id NOT IN (SELECT p.id FROM parts p + JOIN invoice i ON (p.id = i.parts_id)) + AND p.id NOT IN (SELECT p.id FROM parts p + JOIN assembly a ON (p.id = a.parts_id)) + AND p.id NOT IN (SELECT p.id FROM parts p + JOIN orderitems o ON (p.id = o.parts_id)) + AND p.id NOT IN (SELECT p.id FROM parts p + JOIN jcitems j ON (p.id = j.parts_id))|; + } + + if ($form->{itemstatus} eq 'active') { + $where .= " AND p.obsolete = '0'"; + } + if ($form->{itemstatus} eq 'obsolete') { + $where .= " AND p.obsolete = '1'"; + } + if ($form->{itemstatus} eq 'onhand') { + $where .= " AND p.onhand > 0"; + } + if ($form->{itemstatus} eq 'short') { + $where .= " AND p.onhand < p.rop"; + } + + my $makemodelflds = qq|, '', ''|;; + my $makemodeljoin; + + if (($form->{make} ne "") || $form->{l_make} || ($form->{model} ne "") || $form->{l_model}) { + $makemodelflds = qq|, m.make, m.model|; + $makemodeljoin = qq|LEFT JOIN makemodel m ON (m.parts_id = p.id)|; + + if ($form->{make} ne "") { + $var = $form->like(lc $form->{make}); + $where .= " AND lower(m.make) LIKE '$var'"; + } + if ($form->{model} ne "") { + $var = $form->like(lc $form->{model}); + $where .= " AND lower(m.model) LIKE '$var'"; + } + } + if ($form->{partsgroup} ne "") { + ($null, $var) = split /--/, $form->{partsgroup}; + $where .= qq| AND p.partsgroup_id = $var|; + } + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my %ordinal = ( 'partnumber' => 2, + 'description' => 3, + 'bin' => 6, + 'priceupdate' => 13, + 'drawing' => 15, + 'microfiche' => 16, + 'partsgroup' => 18, + 'make' => 21, + 'model' => 22, + 'assemblypartnumber' => 23 + ); + + my @a = qw(partnumber description); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT curr FROM defaults|; + my ($curr) = $dbh->selectrow_array($query); + $curr =~ s/:.*//; + + my $flds = qq|p.id, p.partnumber, p.description, p.onhand, p.unit, + p.bin, p.sellprice, p.listprice, p.lastcost, p.rop, + p.avgcost, + p.weight, p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, pg.partsgroup, '$curr' AS curr, + c1.accno AS inventory, c2.accno AS income, c3.accno AS expense, + p.notes + $makemodelflds $assemblyflds + |; + + $query = qq|SELECT $flds + FROM parts p + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN chart c1 ON (c1.id = p.inventory_accno_id) + LEFT JOIN chart c2 ON (c2.id = p.income_accno_id) + LEFT JOIN chart c3 ON (c3.id = p.expense_accno_id) + $makemodeljoin + WHERE $where + ORDER BY $sortorder|; + + # redo query for components report + if ($form->{searchitems} eq 'component') { + + $flds =~ s/p.onhand/a.qty AS onhand/; + + $query = qq|SELECT $flds + FROM assembly a + JOIN parts p ON (a.parts_id = p.id) + JOIN parts p1 ON (a.id = p1.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN chart c1 ON (c1.id = p.inventory_accno_id) + LEFT JOIN chart c2 ON (c2.id = p.income_accno_id) + LEFT JOIN chart c3 ON (c3.id = p.expense_accno_id) + $makemodeljoin + WHERE $where + ORDER BY $sortorder|; + } + + + # rebuild query for bought and sold items + if ($form->{bought} || $form->{sold} || $form->{onorder} || $form->{ordered} || $form->{rfq} || $form->{quoted}) { + + $form->sort_order(); + @a = qw(partnumber description curr employee name serialnumber id); + push @a, "invnumber" if ($form->{bought} || $form->{sold}); + push @a, "ordnumber" if ($form->{onorder} || $form->{ordered}); + push @a, "quonumber" if ($form->{rfq} || $form->{quoted}); + + %ordinal = ( 'partnumber' => 2, + 'description' => 3, + 'serialnumber' => 4, + 'bin' => 7, + 'priceupdate' => 14, + 'partsgroup' => 19, + 'invnumber' => 20, + 'ordnumber' => 21, + 'quonumber' => 22, + 'name' => 24, + 'employee' => 25, + 'curr' => 26, + 'make' => 29, + 'model' => 30 + ); + + $sortorder = $form->sort_order(\@a, \%ordinal); + + my $union = ""; + $query = ""; + + if ($form->{bought} || $form->{sold}) { + + my $invwhere = "$where"; + my $transdate = ($form->{method} eq 'accrual') ? "transdate" : "datepaid"; + + $invwhere .= " AND i.assemblyitem = '0'"; + $invwhere .= " AND a.$transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $invwhere .= " AND a.$transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $invwhere .= " AND lower(i.description) LIKE '$var'"; + } + + if ($form->{open} || $form->{closed}) { + if ($form->{open} && $form->{closed}) { + if ($form->{method} eq 'cash') { + $invwhere .= " AND a.amount = a.paid"; + } + } else { + if ($form->{open}) { + if ($form->{method} eq 'cash') { + $invwhere .= " AND a.id = 0"; + } else { + $invwhere .= " AND NOT a.amount = a.paid"; + } + } else { + $invwhere .= " AND a.amount = a.paid"; + } + } + } else { + $invwhere .= " AND a.id = 0"; + } + + my $flds = qq|p.id, p.partnumber, i.description, i.serialnumber, + i.qty AS onhand, i.unit, p.bin, i.sellprice, + p.listprice, p.lastcost, p.rop, p.weight, + p.avgcost, + p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, + pg.partsgroup, a.invnumber, a.ordnumber, a.quonumber, + i.trans_id, ct.name, e.name AS employee, a.curr, a.till, + p.notes + $makemodelfld|; + + + if ($form->{bought}) { + my $rflds = $flds; + $rflds =~ s/i.qty AS onhand/i.qty * -1 AS onhand/; + + $query = qq| + SELECT $rflds, 'ir' AS module, '' AS type, + (SELECT sell FROM exchangerate ex + WHERE ex.curr = a.curr + AND ex.transdate = a.$transdate) AS exchangerate, + i.discount + FROM invoice i + JOIN parts p ON (p.id = i.parts_id) + JOIN ap a ON (a.id = i.trans_id) + JOIN vendor ct ON (a.vendor_id = ct.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $makemodeljoin + WHERE $invwhere|; + $union = " + UNION ALL"; + } + + if ($form->{sold}) { + $query .= qq|$union + SELECT $flds, 'is' AS module, '' AS type, + (SELECT buy FROM exchangerate ex + WHERE ex.curr = a.curr + AND ex.transdate = a.$transdate) AS exchangerate, + i.discount + FROM invoice i + JOIN parts p ON (p.id = i.parts_id) + JOIN ar a ON (a.id = i.trans_id) + JOIN customer ct ON (a.customer_id = ct.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $makemodeljoin + WHERE $invwhere|; + $union = " + UNION ALL"; + } + } + + if ($form->{onorder} || $form->{ordered}) { + my $ordwhere = "$where + AND a.quotation = '0'"; + $ordwhere .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $ordwhere .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $ordwhere .= " AND lower(i.description) LIKE '$var'"; + } + + if ($form->{open} || $form->{closed}) { + unless ($form->{open} && $form->{closed}) { + $ordwhere .= " AND a.closed = '0'" if $form->{open}; + $ordwhere .= " AND a.closed = '1'" if $form->{closed}; + } + } else { + $ordwhere .= " AND a.id = 0"; + } + + $flds = qq|p.id, p.partnumber, i.description, i.serialnumber, + i.qty AS onhand, i.unit, p.bin, i.sellprice, + p.listprice, p.lastcost, p.rop, p.weight, + p.avgcost, + p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, + pg.partsgroup, '' AS invnumber, a.ordnumber, a.quonumber, + i.trans_id, ct.name, e.name AS employee, a.curr, '0' AS till, + p.notes + $makemodelfld|; + + if ($form->{ordered}) { + $query .= qq|$union + SELECT $flds, 'oe' AS module, 'sales_order' AS type, + (SELECT buy FROM exchangerate ex + WHERE ex.curr = a.curr + AND ex.transdate = a.transdate) AS exchangerate, + i.discount + FROM orderitems i + JOIN parts p ON (i.parts_id = p.id) + JOIN oe a ON (i.trans_id = a.id) + JOIN customer ct ON (a.customer_id = ct.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $makemodeljoin + WHERE $ordwhere + AND a.customer_id > 0|; + $union = " + UNION ALL"; + } + + if ($form->{onorder}) { + $flds = qq|p.id, p.partnumber, i.description, i.serialnumber, + i.qty AS onhand, i.unit, p.bin, i.sellprice, + p.listprice, p.lastcost, p.rop, p.weight, + p.avgcost, + p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, + pg.partsgroup, '' AS invnumber, a.ordnumber, a.quonumber, + i.trans_id, ct.name,e.name AS employee, a.curr, '0' AS till, + p.notes + $makemodelfld|; + + $query .= qq|$union + SELECT $flds, 'oe' AS module, 'purchase_order' AS type, + (SELECT sell FROM exchangerate ex + WHERE ex.curr = a.curr + AND ex.transdate = a.transdate) AS exchangerate, + i.discount + FROM orderitems i + JOIN parts p ON (i.parts_id = p.id) + JOIN oe a ON (i.trans_id = a.id) + JOIN vendor ct ON (a.vendor_id = ct.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $makemodeljoin + WHERE $ordwhere + AND a.vendor_id > 0|; + } + + } + + if ($form->{rfq} || $form->{quoted}) { + my $quowhere = "$where + AND a.quotation = '1'"; + $quowhere .= " AND a.transdate >= '$form->{transdatefrom}'" if $form->{transdatefrom}; + $quowhere .= " AND a.transdate <= '$form->{transdateto}'" if $form->{transdateto}; + + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $quowhere .= " AND lower(i.description) LIKE '$var'"; + } + + if ($form->{open} || $form->{closed}) { + unless ($form->{open} && $form->{closed}) { + $ordwhere .= " AND a.closed = '0'" if $form->{open}; + $ordwhere .= " AND a.closed = '1'" if $form->{closed}; + } + } else { + $ordwhere .= " AND a.id = 0"; + } + + + $flds = qq|p.id, p.partnumber, i.description, i.serialnumber, + i.qty AS onhand, i.unit, p.bin, i.sellprice, + p.listprice, p.lastcost, p.rop, p.weight, + p.avgcost, + p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, + pg.partsgroup, '' AS invnumber, a.ordnumber, a.quonumber, + i.trans_id, ct.name, e.name AS employee, a.curr, '0' AS till, + p.notes + $makemodelfld|; + + if ($form->{quoted}) { + $query .= qq|$union + SELECT $flds, 'oe' AS module, 'sales_quotation' AS type, + (SELECT buy FROM exchangerate ex + WHERE ex.curr = a.curr + AND ex.transdate = a.transdate) AS exchangerate, + i.discount + FROM orderitems i + JOIN parts p ON (i.parts_id = p.id) + JOIN oe a ON (i.trans_id = a.id) + JOIN customer ct ON (a.customer_id = ct.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $makemodeljoin + WHERE $quowhere + AND a.customer_id > 0|; + $union = " + UNION ALL"; + } + + if ($form->{rfq}) { + $flds = qq|p.id, p.partnumber, i.description, i.serialnumber, + i.qty AS onhand, i.unit, p.bin, i.sellprice, + p.listprice, p.lastcost, p.rop, p.weight, + p.avgcost, + p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, + pg.partsgroup, '' AS invnumber, a.ordnumber, a.quonumber, + i.trans_id, ct.name, e.name AS employee, a.curr, '0' AS till, + p.notes + $makemodelfld|; + + $query .= qq|$union + SELECT $flds, 'oe' AS module, 'request_quotation' AS type, + (SELECT sell FROM exchangerate ex + WHERE ex.curr = a.curr + AND ex.transdate = a.transdate) AS exchangerate, + i.discount + FROM orderitems i + JOIN parts p ON (i.parts_id = p.id) + JOIN oe a ON (i.trans_id = a.id) + JOIN vendor ct ON (a.vendor_id = ct.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $makemodeljoin + WHERE $quowhere + AND a.vendor_id > 0|; + } + + } + + $query .= qq| + ORDER BY $sortorder|; + + } + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (pt.chart_id = c.id) + WHERE pt.parts_id = ? + ORDER BY accno|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $pth->execute($ref->{id}); + while (($accno) = $pth->fetchrow_array) { + $ref->{tax} .= "$accno "; + } + $pth->finish; + + push @{ $form->{parts} }, $ref; + } + $sth->finish; + + @a = (); + + # include individual items for assembly + if (($form->{searchitems} eq 'assembly') && $form->{individual}) { + + if ($form->{sold} || $form->{ordered} || $form->{quoted}) { + $flds = qq|p.id, p.partnumber, p.description, p.onhand AS perassembly, p.unit, + p.bin, p.sellprice, p.listprice, p.lastcost, p.rop, + p.avgcost, + p.weight, p.priceupdate, p.image, p.drawing, p.microfiche, + p.assembly, pg.partsgroup, p.notes + $makemodelflds $assemblyflds + |; + } else { + # replace p.onhand with a.qty AS onhand + $flds =~ s/p.onhand/a.qty AS perassembly/; + } + + for (@{ $form->{parts} }) { + push @a, $_; + $_->{perassembly} = 1; + $flds =~ s/p\.onhand*AS perassembly/p\.onhand, a\.qty AS perassembly/; + push @a, &include_assembly($dbh, $myconfig, $form, $_->{id}, $flds, $makemodeljoin); + push @a, {id => $_->{id}, assemblyitem => 1}; # this is just for + # adding a blank line + } + + # copy assemblies to $form->{parts} + @{ $form->{parts} } = @a; + + } + + + @a = (); + if (($form->{warehouse} ne "") || $form->{l_warehouse}) { + + if ($form->{warehouse} ne "") { + ($null, $var) = split /--/, $form->{warehouse}; + $var *= 1; + $query = qq|SELECT SUM(qty) AS onhand, '$null' AS description + FROM inventory + WHERE warehouse_id = $var + AND parts_id = ?|; + } else { + $query = qq|SELECT SUM(i.qty) AS onhand, w.description AS warehouse + FROM inventory i + JOIN warehouse w ON (w.id = i.warehouse_id) + WHERE i.parts_id = ? + GROUP BY w.description|; + } + + $sth = $dbh->prepare($query) || $form->dberror($query); + + for (@{ $form->{parts} }) { + + $sth->execute($_->{id}) || $form->dberror($query); + + if ($form->{warehouse} ne "") { + + $ref = $sth->fetchrow_hashref(NAME_lc); + if ($ref->{onhand} != 0) { + $_->{onhand} = $ref->{onhand}; + push @a, $_; + } + + } else { + + push @a, $_; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + if ($ref->{onhand} > 0) { + push @a, $ref; + } + } + } + + $sth->finish; + } + + @{ $form->{parts} } = @a; + + } + + $dbh->disconnect; + +} + + +sub include_assembly { + my ($dbh, $myconfig, $form, $id, $flds, $makemodeljoin) = @_; + + $form->{stagger}++; + if ($form->{stagger} > $form->{pncol}) { + $form->{pncol} = $form->{stagger}; + } + + $form->{$id} = 1; + + my %oid = ('Pg' => 'a.oid', + 'PgPP' => 'a.oid', + 'Oracle' => 'a.rowid', + 'DB2' => '1=1' + ); + + my @a = qw(partnumber description bin); + if ($form->{sort} eq 'partnumber') { + $sortorder = "$oid{$myconfig->{dbdriver}}"; + } else { + @a = grep !/$form->{sort}/, @a; + $sortorder = "$form->{sort} $form->{direction}, ". join ',', @a; + } + + @a = (); + my $query = qq|SELECT $flds + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + LEFT JOIN partsgroup pg ON (pg.id = p.id) + LEFT JOIN chart c1 ON (c1.id = p.inventory_accno_id) + LEFT JOIN chart c2 ON (c2.id = p.income_accno_id) + LEFT JOIN chart c3 ON (c3.id = p.expense_accno_id) + $makemodeljoin + WHERE a.id = $id + ORDER BY $sortorder|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{assemblyitem} = 1; + $ref->{stagger} = $form->{stagger}; + + push @a, $ref; + if ($ref->{assembly} && !$form->{$ref->{id}}) { + push @a, &include_assembly($dbh, $myconfig, $form, $ref->{id}, $flds, $makemodeljoin); + if ($form->{stagger} > $form->{pncol}) { + $form->{pncol} = $form->{stagger}; + } + } + } + $sth->finish; + + $form->{$id} = 0; + $form->{stagger}--; + + @a; + +} + + +sub requirements { + my ($self, $myconfig, $form) = @_; + + my $null; + my $var; + my $ref; + + my $where = qq|p.obsolete = '0'|; + my $dwhere; + + for (qw(partnumber description)) { + if ($form->{$_} ne "") { + $var = $form->like(lc $form->{$_}); + $where .= qq| AND lower(p.$_) LIKE '$var'|; + } + } + + if ($form->{partsgroup} ne "") { + ($null, $var) = split /--/, $form->{partsgroup}; + $where .= qq| AND p.partsgroup_id = $var|; + } + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my ($transdatefrom, $transdateto); + if ($form->{year}) { + ($transdatefrom, $transdateto) = $form->from_to($form->{year}, '01', 12); + + $dwhere = qq| AND a.transdate >= '$transdatefrom' + AND a.transdate <= '$transdateto'|; + } + + $query = qq|SELECT p.id, p.partnumber, p.description, + sum(i.qty) AS qty, p.onhand, + extract(MONTH FROM a.transdate) AS month, + '0' AS so, '0' AS po + FROM invoice i + JOIN parts p ON (p.id = i.parts_id) + JOIN ar a ON (a.id = i.trans_id) + WHERE $where + $dwhere + AND p.inventory_accno_id > 0 + GROUP BY p.id, p.partnumber, p.description, p.onhand, + extract(MONTH FROM a.transdate)|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my %parts; + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $parts{$ref->{id}} = $ref; + } + $sth->finish; + + my %ofld = ( customer => so, + vendor => po ); + + for (qw(customer vendor)) { + $query = qq|SELECT p.id, p.partnumber, p.description, + sum(qty) - sum(ship) AS $ofld{$_}, p.onhand, + 0 AS month + FROM orderitems i + JOIN parts p ON (p.id = i.parts_id) + JOIN oe a ON (a.id = i.trans_id) + WHERE $where + AND p.inventory_accno_id > 0 + AND p.assembly = '0' + AND a.closed = '0' + AND a.${_}_id > 0 + GROUP BY p.id, p.partnumber, p.description, p.onhand, + month|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + if (exists $parts{$ref->{id}}->{$ofld{$_}}) { + $parts{$ref->{id}}->{$ofld{$_}} += $ref->{$ofld{$_}}; + } else { + $parts{$ref->{id}} = $ref; + } + } + $sth->finish; + } + + # add assemblies from open sales orders + $query = qq|SELECT DISTINCT a.id AS orderid, b.id, i.qty - i.ship AS qty + FROM parts p + JOIN assembly b ON (b.parts_id = p.id) + JOIN orderitems i ON (i.parts_id = b.id) + JOIN oe a ON (a.id = i.trans_id) + WHERE $where + AND (p.inventory_accno_id > 0 OR p.assembly = '1') + AND a.closed = '0'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + &requirements_assembly($dbh, $form, \%parts, $ref->{id}, $ref->{qty}, $where) if $ref->{qty}; + } + $sth->finish; + + $dbh->disconnect; + + for (sort { $parts{$a}->{$form->{sort}} cmp $parts{$b}->{$form->{sort}} } keys %parts) { + push @{ $form->{parts} }, $parts{$_}; + } + +} + + +sub requirements_assembly { + my ($dbh, $form, $parts, $id, $qty, $where) = @_; + + # assemblies + my $query = qq|SELECT p.id, p.partnumber, p.description, + a.qty * $qty AS so, p.onhand, p.assembly, + p.partsgroup_id + FROM assembly a + JOIN parts p ON (p.id = a.parts_id) + WHERE $where + AND a.id = $id + AND p.inventory_accno_id > 0 + + UNION + + SELECT p.id, p.partnumber, p.description, + a.qty * $qty AS so, p.onhand, p.assembly, + p.partsgroup_id + FROM assembly a + JOIN parts p ON (p.id = a.parts_id) + WHERE a.id = $id + AND p.assembly = '1'|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + if ($ref->{assembly}) { + &requirements_assembly($dbh, $form, $parts, $ref->{id}, $ref->{so}, $where); + next; + } + + if (exists $parts->{$ref->{id}}{so}) { + $parts->{$ref->{id}}{so} += $ref->{so}; + } else { + $parts->{$ref->{id}} = $ref; + } + } + $sth->finish; + +} + + +sub create_links { + my ($self, $module, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $ref; + + my $query = qq|SELECT accno, description, link + FROM chart + WHERE link LIKE '%$module%' + ORDER BY accno|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + foreach my $key (split /:/, $ref->{link}) { + if ($key =~ /$module/) { + push @{ $form->{"${module}_links"}{$key} }, { accno => $ref->{accno}, + description => $ref->{description} }; + } + } + } + $sth->finish; + + if ($form->{item} ne 'assembly') { + $query = qq|SELECT count(*) FROM vendor|; + my ($count) = $dbh->selectrow_array($query); + + if ($count < $myconfig->{vclimit}) { + $query = qq|SELECT id, name + FROM vendor + ORDER BY name|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_vendor} }, $ref; + } + $sth->finish; + } + } + + # pricegroups, customers + $query = qq|SELECT count(*) FROM customer|; + ($count) = $dbh->selectrow_array($query); + + if ($count < $myconfig->{vclimit}) { + $query = qq|SELECT id, name + FROM customer + ORDER BY name|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_customer} }, $ref; + } + $sth->finish; + } + + $query = qq|SELECT id, pricegroup + FROM pricegroup + ORDER BY pricegroup|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_pricegroup} }, $ref; + } + $sth->finish; + + + if ($form->{id}) { + $query = qq|SELECT weightunit, curr AS currencies + FROM defaults|; + ($form->{weightunit}, $form->{currencies}) = $dbh->selectrow_array($query); + + } else { + $query = qq|SELECT d.weightunit, current_date AS priceupdate, + d.curr AS currencies, + c1.accno AS inventory_accno, c1.description AS inventory_description, + c2.accno AS income_accno, c2.description AS income_description, + c3.accno AS expense_accno, c3.description AS expense_description + FROM defaults d + LEFT JOIN chart c1 ON (d.inventory_accno_id = c1.id) + LEFT JOIN chart c2 ON (d.income_accno_id = c2.id) + LEFT JOIN chart c3 ON (d.expense_accno_id = c3.id)|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (qw(weightunit priceupdate currencies)) { $form->{$_} = $ref->{$_} } + # setup accno hash, {amount} is used in create_links + for (qw(inventory income expense)) { $form->{amount}{"IC_$_"} = { accno => $ref->{"${_}_accno"}, description => $ref->{"${_}_description"} } } + + $sth->finish; + } + + $dbh->disconnect; + +} + + +sub get_warehouses { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT id, description + FROM warehouse|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_warehouse} }, $ref; + } + $sth->finish; + + $dbh->disconnect; + +} + +1; + diff --git a/LedgerSMB/IR.pm b/LedgerSMB/IR.pm new file mode 100755 index 00000000..d06a233c --- /dev/null +++ b/LedgerSMB/IR.pm @@ -0,0 +1,1123 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Inventory received module +# +#====================================================================== + +package IR; + + +sub post_invoice { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off autocommit + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + my $ref; + my $null; + my $project_id; + my $exchangerate = 0; + my $allocated; + my $taxrate; + my $taxamount; + my $diff = 0; + my $item; + my $invoice_id; + my $keepcleared; + + ($null, $form->{employee_id}) = split /--/, $form->{employee}; + unless ($form->{employee_id}) { + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + } + + ($null, $form->{department_id}) = split(/--/, $form->{department}); + $form->{department_id} *= 1; + + $query = qq|SELECT fxgain_accno_id, fxloss_accno_id + FROM defaults d|; + my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query); + + $query = qq|SELECT inventory_accno_id, income_accno_id, expense_accno_id + FROM parts + WHERE id = ?|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + my %updparts = (); + + if ($form->{id}) { + $keepcleared = 1; + $query = qq|SELECT id FROM ap + WHERE id = $form->{id}|; + + if ($dbh->selectrow_array($query)) { + $query = qq|SELECT p.id, p.inventory_accno_id, p.income_accno_id + FROM invoice i + JOIN parts p ON (p.id = i.parts_id) + WHERE i.trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + while ($ref = $sth->fetchrow_hashref) { + if ($ref->{inventory_accno_id} && $ref->{income_accno_id}) { + $updparts{$ref->{id}} = 1; + } + } + $sth->finish; + + &reverse_invoice($dbh, $form); + } else { + $query = qq|INSERT INTO ap (id) + VALUES ($form->{id})|; + $dbh->do($query) || $form->dberror($query); + } + } + + my $uid = localtime; + $uid .= "$$"; + + if (! $form->{id}) { + + $query = qq|INSERT INTO ap (invnumber, employee_id) + VALUES ('$uid', (SELECT id FROM employee + WHERE login = '$form->{login}'))|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM ap + WHERE invnumber = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{id}) = $sth->fetchrow_array; + $sth->finish; + + } + + my $amount; + my $grossamount; + my $allocated; + my $invamount = 0; + my $invnetamount = 0; + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{exchangerate} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, 'sell'); + } + + $form->{exchangerate} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{exchangerate}); + + for my $i (1 .. $form->{rowcount}) { + $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}); + + if ($form->{"qty_$i"}) { + + $pth->execute($form->{"id_$i"}); + $ref = $pth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { + $form->{"${_}_$i"} = $ref->{$_}; + } + $pth->finish; + + # project + $project_id = 'NULL'; + if ($form->{"projectnumber_$i"} ne "") { + ($null, $project_id) = split /--/, $form->{"projectnumber_$i"}; + } + + # undo discount formatting + $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100; + + # keep entered selling price + my $fxsellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"}); + + my ($dec) = ($fxsellprice =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + # deduct discount + $form->{"sellprice_$i"} = $fxsellprice - $form->round_amount($fxsellprice * $form->{"discount_$i"}, $decimalplaces); + + # linetotal + my $fxlinetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2); + + $amount = $fxlinetotal * $form->{exchangerate}; + my $linetotal = $form->round_amount($amount, 2); + $fxdiff += $amount - $linetotal; + + @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + + $ml = 1; + $tax = 0; + $fxtax = 0; + + for (0 .. 1) { + $taxrate = 0; + + # add tax rates + for (@taxaccounts) { + $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0; + } + + if ($form->{taxincluded}) { + $tax += $amount = $linetotal * ($taxrate / (1 + ($taxrate * $ml))); + $form->{"sellprice_$i"} -= $amount / $form->{"qty_$i"}; + } else { + $tax += $amount = $linetotal * $taxrate; + $fxtax += $fxlinetotal * $taxrate; + } + + for (@taxaccounts) { + $form->{acc_trans}{$form->{id}}{$_}{amount} += $amount * $form->{"${_}_rate"} / $taxrate if ($form->{"${_}_rate"} * $ml) > 0; + } + + $ml = -1; + } + + $grossamount = $form->round_amount($linetotal, 2); + + if ($form->{taxincluded}) { + $amount = $form->round_amount($tax, 2); + $linetotal -= $form->round_amount($tax - $diff, 2); + $diff = ($amount - $tax); + } + + $amount = $form->round_amount($linetotal, 2); + $allocated = 0; + + # adjust and round sellprice + $form->{"sellprice_$i"} = $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate}, $decimalplaces); + + # save detail record in invoice table + $query = qq|INSERT INTO invoice (description) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM invoice + WHERE description = '$uid'|; + ($invoice_id) = $dbh->selectrow_array($query); + + $query = qq|UPDATE invoice SET + trans_id = $form->{id}, + parts_id = $form->{"id_$i"}, + description = |.$dbh->quote($form->{"description_$i"}).qq|, + qty = $form->{"qty_$i"} * -1, + sellprice = $form->{"sellprice_$i"}, + fxsellprice = $fxsellprice, + discount = $form->{"discount_$i"}, + allocated = $allocated, + unit = |.$dbh->quote($form->{"unit_$i"}).qq|, + deliverydate = |.$form->dbquote($form->{"deliverydate_$i"}, SQL_DATE).qq|, + project_id = $project_id, + serialnumber = |.$dbh->quote($form->{"serialnumber_$i"}).qq|, + notes = |.$dbh->quote($form->{"notes_$i"}).qq| + WHERE id = $invoice_id|; + $dbh->do($query) || $form->dberror($query); + + + if ($form->{"inventory_accno_id_$i"}) { + + # add purchase to inventory + push @{ $form->{acc_trans}{lineitems} }, { + chart_id => $form->{"inventory_accno_id_$i"}, + amount => $amount, + fxgrossamount => $fxlinetotal + $form->round_amount($fxtax, 2), + grossamount => $grossamount, + project_id => $project_id, + invoice_id => $invoice_id }; + + + $updparts{$form->{"id_$i"}} = 1; + + # update parts table + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $form->{"id_$i"}|, + $form->{"qty_$i"}) unless $form->{shipped}; + + + # check if we sold the item + $query = qq|SELECT i.id, i.qty, i.allocated, i.trans_id, i.project_id, + p.inventory_accno_id, p.expense_accno_id, a.transdate + FROM invoice i + JOIN parts p ON (p.id = i.parts_id) + JOIN ar a ON (a.id = i.trans_id) + WHERE i.parts_id = $form->{"id_$i"} + AND (i.qty + i.allocated) > 0 + ORDER BY transdate|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $totalqty = $form->{"qty_$i"}; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + my $qty = $ref->{qty} + $ref->{allocated}; + + if (($qty - $totalqty) > 0) { + $qty = $totalqty; + } + + $linetotal = $form->round_amount($form->{"sellprice_$i"} * $qty, 2); + $ref->{project_id} ||= 'NULL'; + + # add entry for inventory, this one is for the sold item + if ($linetotal) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, project_id, invoice_id) + VALUES ($ref->{trans_id}, $ref->{inventory_accno_id}, + $linetotal, '$ref->{transdate}', $ref->{project_id}, + $invoice_id)|; + $dbh->do($query) || $form->dberror($query); + + # add expense + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, project_id, invoice_id) + VALUES ($ref->{trans_id}, $ref->{expense_accno_id}, + |. ($linetotal * -1) .qq|, '$ref->{transdate}', + $ref->{project_id}, $invoice_id)|; + $dbh->do($query) || $form->dberror($query); + } + + # update allocated for sold item + $form->update_balance($dbh, + "invoice", + "allocated", + qq|id = $ref->{id}|, + $qty * -1); + + $allocated += $qty; + + last if (($totalqty -= $qty) <= 0); + } + + $sth->finish; + + } else { + + # add purchase to expense + push @{ $form->{acc_trans}{lineitems} }, { + chart_id => $form->{"expense_accno_id_$i"}, + amount => $amount, + fxgrossamount => $fxlinetotal + $form->round_amount($fxtax, 2), + grossamount => $grossamount, + project_id => $project_id, + invoice_id => $invoice_id }; + + } + } + } + + $form->{paid} = 0; + for $i (1 .. $form->{paidaccounts}) { + $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"}); + $form->{paid} += $form->{"paid_$i"}; + $form->{datepaid} = $form->{"datepaid_$i"} if ($form->{"datepaid_$i"}); + } + + # add lineitems + tax + $amount = 0; + $grossamount = 0; + $fxgrossamount = 0; + for (@{ $form->{acc_trans}{lineitems} }) { + $amount += $_->{amount}; + $grossamount += $_->{grossamount}; + $fxgrossamount += $_->{fxgrossamount}; + } + $invnetamount = $amount; + + $amount = 0; + for (split / /, $form->{taxaccounts}) { + $amount += $form->{acc_trans}{$form->{id}}{$_}{amount} = $form->round_amount($form->{acc_trans}{$form->{id}}{$_}{amount}, 2); + $form->{acc_trans}{$form->{id}}{$_}{amount} *= -1; + } + $invamount = $invnetamount + $amount; + + $diff = 0; + if ($form->{taxincluded}) { + $diff = $form->round_amount($grossamount - $invamount, 2); + $invamount += $diff; + } + $fxdiff = $form->round_amount($fxdiff,2); + $invnetamount += $fxdiff; + $invamount += $fxdiff; + + if ($form->round_amount($form->{paid} - $fxgrossamount,2) == 0) { + $form->{paid} = $invamount; + } else { + $form->{paid} = $form->round_amount($form->{paid} * $form->{exchangerate}, 2); + } + + foreach $ref (sort { $b->{amount} <=> $a->{amount} } @ { $form->{acc_trans}{lineitems} }) { + $amount = $ref->{amount} + $diff + $fxdiff; + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, project_id, invoice_id) + VALUES ($form->{id}, $ref->{chart_id}, $amount * -1, + '$form->{transdate}', $ref->{project_id}, $ref->{invoice_id})|; + $dbh->do($query) || $form->dberror($query); + $diff = 0; + $fxdiff = 0; + } + + $form->{payables} = $invamount; + + delete $form->{acc_trans}{lineitems}; + + # update exchangerate + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate}); + } + + # record payable + if ($form->{payables}) { + ($accno) = split /--/, $form->{AP}; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate) + VALUES ($form->{id}, + (SELECT id FROM chart + WHERE accno = '$accno'), + $form->{payables}, '$form->{transdate}')|; + $dbh->do($query) || $form->dberror($query); + } + + foreach my $trans_id (keys %{$form->{acc_trans}}) { + foreach my $accno (keys %{ $form->{acc_trans}{$trans_id} }) { + $amount = $form->round_amount($form->{acc_trans}{$trans_id}{$accno}{amount}, 2); + if ($amount) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate) + VALUES ($trans_id, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount, '$form->{transdate}')|; + $dbh->do($query) || $form->dberror($query); + } + } + } + + # if there is no amount but a payment record payable + if ($invamount == 0) { + $form->{payables} = 1; + } + + my $cleared = 0; + + # record payments and offsetting AP + for my $i (1 .. $form->{paidaccounts}) { + + if ($form->{"paid_$i"}) { + my ($accno) = split /--/, $form->{"AP_paid_$i"}; + $form->{"datepaid_$i"} = $form->{transdate} unless ($form->{"datepaid_$i"}); + $form->{datepaid} = $form->{"datepaid_$i"}; + + $exchangerate = 0; + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{"exchangerate_$i"} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'sell'); + + $form->{"exchangerate_$i"} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{"exchangerate_$i"}); + } + + + # record AP + $amount = ($form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2)) * -1; + + if ($form->{payables}) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$form->{AP}'), + $amount, '$form->{"datepaid_$i"}')|; + $dbh->do($query) || $form->dberror($query); + } + + if ($keepcleared) { + $cleared = ($form->{"cleared_$i"}) ? 1 : 0; + } + + # record payment + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, + source, memo, cleared) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $form->{"paid_$i"}, '$form->{"datepaid_$i"}', | + .$dbh->quote($form->{"source_$i"}).qq|, | + .$dbh->quote($form->{"memo_$i"}).qq|, '$cleared')|; + $dbh->do($query) || $form->dberror($query); + + # exchangerate difference + $amount = $form->round_amount($form->{"paid_$i"} * $form->{"exchangerate_$i"} - $form->{"paid_$i"}, 2); + + if ($amount) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, source, fx_transaction, cleared) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount, '$form->{"datepaid_$i"}', | + .$dbh->quote($form->{"source_$i"}).qq|, '1', '$cleared')|; + $dbh->do($query) || $form->dberror($query); + } + + # gain/loss + $amount = $form->round_amount($form->round_amount($form->{"paid_$i"} * $form->{exchangerate},2) - $form->round_amount($form->{"paid_$i"} * $form->{"exchangerate_$i"},2), 2); + + if ($amount) { + my $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id; + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, fx_transaction, cleared) + VALUES ($form->{id}, $accno_id, + $amount, '$form->{"datepaid_$i"}', '1', '$cleared')|; + $dbh->do($query) || $form->dberror($query); + } + + # update exchange rate + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + $form->update_exchangerate($dbh, $form->{currency}, $form->{"datepaid_$i"}, 0, $form->{"exchangerate_$i"}); + } + } + } + + # set values which could be empty + $form->{taxincluded} *= 1; + + $form->{invnumber} = $form->update_defaults($myconfig, "vinumber", $dbh) unless $form->{invnumber}; + + # save AP record + $query = qq|UPDATE ap set + invnumber = |.$dbh->quote($form->{invnumber}).qq|, + ordnumber = |.$dbh->quote($form->{ordnumber}).qq|, + quonumber = |.$dbh->quote($form->{quonumber}).qq|, + transdate = '$form->{transdate}', + vendor_id = $form->{vendor_id}, + amount = $invamount, + netamount = $invnetamount, + paid = $form->{paid}, + datepaid = |.$form->dbquote($form->{datepaid}, SQL_DATE).qq|, + duedate = |.$form->dbquote($form->{duedate}, SQL_DATE).qq|, + invoice = '1', + shippingpoint = |.$dbh->quote($form->{shippingpoint}).qq|, + shipvia = |.$dbh->quote($form->{shipvia}).qq|, + taxincluded = '$form->{taxincluded}', + notes = |.$dbh->quote($form->{notes}).qq|, + intnotes = |.$dbh->quote($form->{intnotes}).qq|, + curr = '$form->{currency}', + department_id = $form->{department_id}, + employee_id = $form->{employee_id}, + language_code = '$form->{language_code}', + ponumber = |.$dbh->quote($form->{ponumber}).qq| + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # add shipto + $form->{name} = $form->{vendor}; + $form->{name} =~ s/--$form->{vendor_id}//; + $form->add_shipto($dbh, $form->{id}); + + my %audittrail = ( tablename => 'ap', + reference => $form->{invnumber}, + formname => $form->{type}, + action => 'posted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $rc = $dbh->commit; + + foreach $item (keys %updparts) { + $query = qq|UPDATE parts SET + avgcost = avgcost($item), + lastcost = lastcost($item) + WHERE id = $item|; + $dbh->do($query) || $form->dberror($query); + $dbh->commit; + } + + $dbh->disconnect; + $rc; + +} + + + +sub reverse_invoice { + my ($dbh, $form) = @_; + + my $query = qq|SELECT id FROM ap + WHERE id = $form->{id}|; + my ($id) = $dbh->selectrow_array($query); + + return unless $id; + + # reverse inventory items + $query = qq|SELECT i.parts_id, p.inventory_accno_id, p.expense_accno_id, + i.qty, i.allocated, i.sellprice, i.project_id + FROM invoice i, parts p + WHERE i.parts_id = p.id + AND i.trans_id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $netamount = 0; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $netamount += $form->round_amount($ref->{sellprice} * $ref->{qty} * -1, 2); + + if ($ref->{inventory_accno_id}) { + # update onhand + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $ref->{parts_id}|, + $ref->{qty}); + + # if $ref->{allocated} > 0 than we sold that many items + if ($ref->{allocated} > 0) { + + # get references for sold items + $query = qq|SELECT i.id, i.trans_id, i.allocated, a.transdate + FROM invoice i, ar a + WHERE i.parts_id = $ref->{parts_id} + AND i.allocated < 0 + AND i.trans_id = a.id + ORDER BY transdate DESC|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $pthref = $sth->fetchrow_hashref(NAME_lc)) { + my $qty = $ref->{allocated}; + + if (($ref->{allocated} + $pthref->{allocated}) > 0) { + $qty = $pthref->{allocated} * -1; + } + + my $amount = $form->round_amount($ref->{sellprice} * $qty, 2); + + #adjust allocated + $form->update_balance($dbh, + "invoice", + "allocated", + qq|id = $pthref->{id}|, + $qty); + + # add reversal for sale + $ref->{project_id} *= 1; + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, project_id) + VALUES ($pthref->{trans_id}, $ref->{expense_accno_id}, + $amount, '$form->{transdate}', $ref->{project_id})|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, project_id) + VALUES ($pthref->{trans_id}, $ref->{inventory_accno_id}, + $amount * -1, '$form->{transdate}', $ref->{project_id})|; + $dbh->do($query) || $form->dberror($query); + + last if (($ref->{allocated} -= $qty) <= 0); + } + $sth->finish; + } + } + } + $sth->finish; + + # delete acc_trans + $query = qq|DELETE FROM acc_trans + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete invoice entries + $query = qq|DELETE FROM invoice + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM shipto + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $dbh->commit; + +} + + + +sub delete_invoice { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my %audittrail = ( tablename => 'ap', + reference => $form->{invnumber}, + formname => $form->{type}, + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $query = qq|SELECT parts_id FROM invoice + WHERE trans_id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $item; + my %updparts = (); + while (($item) = $sth->fetchrow_array) { + $updparts{$item} = 1; + } + $sth->finish; + + &reverse_invoice($dbh, $form); + + # delete AP record + $query = qq|DELETE FROM ap + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete spool files + $query = qq|SELECT spoolfile FROM status + WHERE trans_id = $form->{id} + AND spoolfile IS NOT NULL|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $spoolfile; + my @spoolfiles = (); + + while (($spoolfile) = $sth->fetchrow_array) { + push @spoolfiles, $spoolfile; + } + $sth->finish; + + # delete status entries + $query = qq|DELETE FROM status + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + + if ($rc) { + foreach $item (keys %updparts) { + $query = qq|UPDATE parts SET + avgcost = avgcost($item), + lastcost = lastcost($item) + WHERE id = $item|; + $dbh->do($query) || $form->dberror($query); + $dbh->commit; + } + + foreach $spoolfile (@spoolfiles) { + unlink "$spool/$spoolfile" if $spoolfile; + } + } + + $dbh->disconnect; + + $rc; + +} + + + +sub retrieve_invoice { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + + if ($form->{id}) { + # get default accounts and last invoice number + $query = qq|SELECT (SELECT c.accno FROM chart c + WHERE d.inventory_accno_id = c.id) AS inventory_accno, + (SELECT c.accno FROM chart c + WHERE d.income_accno_id = c.id) AS income_accno, + (SELECT c.accno FROM chart c + WHERE d.expense_accno_id = c.id) AS expense_accno, + (SELECT c.accno FROM chart c + WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, + (SELECT c.accno FROM chart c + WHERE d.fxloss_accno_id = c.id) AS fxloss_accno, + d.curr AS currencies + FROM defaults d|; + } else { + $query = qq|SELECT (SELECT c.accno FROM chart c + WHERE d.inventory_accno_id = c.id) AS inventory_accno, + (SELECT c.accno FROM chart c + WHERE d.income_accno_id = c.id) AS income_accno, + (SELECT c.accno FROM chart c + WHERE d.expense_accno_id = c.id) AS expense_accno, + (SELECT c.accno FROM chart c + WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, + (SELECT c.accno FROM chart c + WHERE d.fxloss_accno_id = c.id) AS fxloss_accno, + d.curr AS currencies, + current_date AS transdate + FROM defaults d|; + } + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { + $form->{$_} = $ref->{$_}; + } + $sth->finish; + + + if ($form->{id}) { + + # retrieve invoice + $query = qq|SELECT a.invnumber, a.transdate, a.duedate, + a.ordnumber, a.quonumber, a.paid, a.taxincluded, a.notes, + a.intnotes, a.curr AS currency, a.vendor_id, a.language_code, + a.ponumber + FROM ap a + WHERE id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { + $form->{$_} = $ref->{$_}; + } + $sth->finish; + + # get shipto + $query = qq|SELECT * FROM shipto + WHERE trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { + $form->{$_} = $ref->{$_}; + } + $sth->finish; + + # retrieve individual items + $query = qq|SELECT + p.partnumber, i.description, i.qty, i.fxsellprice, i.sellprice, + i.parts_id AS id, i.unit, p.bin, i.deliverydate, + pr.projectnumber, + i.project_id, i.serialnumber, i.discount, i.notes, + pg.partsgroup, p.partsgroup_id, p.partnumber AS sku, + p.weight, p.onhand, + p.inventory_accno_id, p.income_accno_id, p.expense_accno_id, + t.description AS partsgrouptranslation + FROM invoice i + JOIN parts p ON (i.parts_id = p.id) + LEFT JOIN project pr ON (i.project_id = pr.id) + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + LEFT JOIN translation t ON (t.trans_id = p.partsgroup_id AND t.language_code = '$form->{language_code}') + WHERE i.trans_id = $form->{id} + ORDER BY i.id|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # exchangerate defaults + &exchangerate_defaults($dbh, $form); + + # price matrix and vendor partnumber + $query = qq|SELECT partnumber + FROM partsvendor + WHERE parts_id = ? + AND vendor_id = $form->{vendor_id}|; + my $pmh = $dbh->prepare($query) || $form->dberror($query); + + # tax rates for part + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (pt.chart_id = c.id) + WHERE pt.parts_id = ?|; + my $tth = $dbh->prepare($query); + + my $ptref; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + my ($dec) = ($ref->{fxsellprice} =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + $tth->execute($ref->{id}); + $ref->{taxaccounts} = ""; + my $taxrate = 0; + + while ($ptref = $tth->fetchrow_hashref(NAME_lc)) { + $ref->{taxaccounts} .= "$ptref->{accno} "; + $taxrate += $form->{"$ptref->{accno}_rate"}; + } + + $tth->finish; + chop $ref->{taxaccounts}; + + # price matrix + $ref->{sellprice} = $form->round_amount($ref->{fxsellprice} * $form->{$form->{currency}}, $decimalplaces); + &price_matrix($pmh, $ref, $decimalplaces, $form); + + $ref->{sellprice} = $ref->{fxsellprice}; + $ref->{qty} *= -1; + + $ref->{partsgroup} = $ref->{partsgrouptranslation} if $ref->{partsgrouptranslation}; + + push @{ $form->{invoice_details} }, $ref; + + } + + $sth->finish; + + } + + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub retrieve_item { + my ($self, $myconfig, $form) = @_; + + my $i = $form->{rowcount}; + my $null; + my $var; + + # don't include assemblies or obsolete parts + my $where = "WHERE p.assembly = '0' AND p.obsolete = '0'"; + + if ($form->{"partnumber_$i"} ne "") { + $var = $form->like(lc $form->{"partnumber_$i"}); + $where .= " AND lower(p.partnumber) LIKE '$var'"; + } + + if ($form->{"description_$i"} ne "") { + $var = $form->like(lc $form->{"description_$i"}); + if ($form->{language_code} ne "") { + $where .= " AND lower(t1.description) LIKE '$var'"; + } else { + $where .= " AND lower(p.description) LIKE '$var'"; + } + } + + if ($form->{"partsgroup_$i"} ne "") { + ($null, $var) = split /--/, $form->{"partsgroup_$i"}; + $var *= 1; + $where .= qq| AND p.partsgroup_id = $var|; + } + + if ($form->{"description_$i"} ne "") { + $where .= " ORDER BY 3"; + } else { + $where .= " ORDER BY 2"; + } + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT p.id, p.partnumber, p.description, + pg.partsgroup, p.partsgroup_id, + p.lastcost AS sellprice, p.unit, p.bin, p.onhand, p.notes, + p.inventory_accno_id, p.income_accno_id, p.expense_accno_id, + p.partnumber AS sku, p.weight, + t1.description AS translation, + t2.description AS grouptranslation + FROM parts p + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + LEFT JOIN translation t1 ON (t1.trans_id = p.id AND t1.language_code = '$form->{language_code}') + LEFT JOIN translation t2 ON (t2.trans_id = p.partsgroup_id AND t2.language_code = '$form->{language_code}') + $where|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # foreign currency + &exchangerate_defaults($dbh, $form); + + # taxes + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (pt.chart_id = c.id) + WHERE pt.parts_id = ?|; + my $tth = $dbh->prepare($query) || $form->dberror($query); + + # price matrix + $query = qq|SELECT p.* + FROM partsvendor p + WHERE p.parts_id = ? + AND vendor_id = $form->{vendor_id}|; + my $pmh = $dbh->prepare($query) || $form->dberror($query); + + my $ref; + my $ptref; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + my ($dec) = ($ref->{sellprice} =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + # get taxes for part + $tth->execute($ref->{id}); + + $ref->{taxaccounts} = ""; + while ($ptref = $tth->fetchrow_hashref(NAME_lc)) { + $ref->{taxaccounts} .= "$ptref->{accno} "; + } + $tth->finish; + chop $ref->{taxaccounts}; + + # get vendor price and partnumber + &price_matrix($pmh, $ref, $decimalplaces, $form); + + $ref->{description} = $ref->{translation} if $ref->{translation}; + $ref->{partsgroup} = $ref->{grouptranslation} if $ref->{grouptranslation}; + + push @{ $form->{item_list} }, $ref; + + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub exchangerate_defaults { + my ($dbh, $form) = @_; + + my $var; + + # get default currencies + my $query = qq|SELECT substr(curr,1,3), curr FROM defaults|; + my $eth = $dbh->prepare($query) || $form->dberror($query); + $eth->execute; + ($form->{defaultcurrency}, $form->{currencies}) = $eth->fetchrow_array; + $eth->finish; + + $query = qq|SELECT sell + FROM exchangerate + WHERE curr = ? + AND transdate = ?|; + my $eth1 = $dbh->prepare($query) || $form->dberror($query); + + $query = qq~SELECT max(transdate || ' ' || sell || ' ' || curr) + FROM exchangerate + WHERE curr = ?~; + my $eth2 = $dbh->prepare($query) || $form->dberror($query); + + # get exchange rates for transdate or max + foreach $var (split /:/, substr($form->{currencies},4)) { + $eth1->execute($var, $form->{transdate}); + ($form->{$var}) = $eth1->fetchrow_array; + if (! $form->{$var} ) { + $eth2->execute($var); + + ($form->{$var}) = $eth2->fetchrow_array; + ($null, $form->{$var}) = split / /, $form->{$var}; + $form->{$var} = 1 unless $form->{$var}; + $eth2->finish; + } + $eth1->finish; + } + + $form->{$form->{currency}} = $form->{exchangerate} if $form->{exchangerate}; + $form->{$form->{currency}} ||= 1; + $form->{$form->{defaultcurrency}} = 1; + +} + + +sub price_matrix { + my ($pmh, $ref, $decimalplaces, $form) = @_; + + $pmh->execute($ref->{id}); + my $mref = $pmh->fetchrow_hashref(NAME_lc); + + if ($mref->{partnumber} ne "") { + $ref->{partnumber} = $mref->{partnumber}; + } + + if ($mref->{lastcost}) { + # do a conversion + $ref->{sellprice} = $form->round_amount($mref->{lastcost} * $form->{$mref->{curr}}, $decimalplaces); + } + $pmh->finish; + + $ref->{sellprice} *= 1; + + # add 0:price to matrix + $ref->{pricematrix} = "0:$ref->{sellprice}"; + +} + + +sub vendor_details { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # get rest for the vendor + my $query = qq|SELECT vendornumber, name, address1, address2, city, state, + zipcode, country, + contact, phone as vendorphone, fax as vendorfax, vendornumber, + taxnumber AS vendortaxnumber, sic_code AS sic, iban, bic, + gifi_accno AS gifi, startdate, enddate + FROM vendor + WHERE id = $form->{vendor_id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { + $form->{$_} = $ref->{$_}; + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub item_links { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, description, link + FROM chart + WHERE link LIKE '%IC%' + ORDER BY accno|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + foreach my $key (split(/:/, $ref->{link})) { + if ($key =~ /IC/) { + push @{ $form->{IC_links}{$key} }, { accno => $ref->{accno}, + description => $ref->{description} }; + } + } + } + + $sth->finish; +} + +1; + diff --git a/LedgerSMB/IS.pm b/LedgerSMB/IS.pm new file mode 100755 index 00000000..5bb7ef63 --- /dev/null +++ b/LedgerSMB/IS.pm @@ -0,0 +1,1684 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Inventory invoicing module +# +#====================================================================== + +package IS; + + +sub invoice_details { + my ($self, $myconfig, $form) = @_; + + $form->{duedate} = $form->{transdate} unless ($form->{duedate}); + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT date '$form->{duedate}' - date '$form->{transdate}' + AS terms, weightunit + FROM defaults|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{terms}, $form->{weightunit}) = $sth->fetchrow_array; + $sth->finish; + + # this is for the template + $form->{invdate} = $form->{transdate}; + + my $tax = 0; + my $item; + my $i; + my @sortlist = (); + my $projectnumber; + my $projectdescription; + my $projectnumber_id; + my $translation; + my $partsgroup; + + my %oid = ( 'Pg' => 'oid', + 'PgPP' => 'oid', + 'Oracle' => 'rowid', + 'DB2' => '1=1' + ); + + my @taxaccounts; + my %taxaccounts; + my $tax; + my $taxrate; + my $taxamount; + + my %translations; + + $query = qq|SELECT p.description, t.description + FROM project p + LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}') + WHERE id = ?|; + my $prh = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|SELECT inventory_accno_id, income_accno_id, + expense_accno_id, assembly, weight FROM parts + WHERE id = ?|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + my $sortby; + + # sort items by project and partsgroup + for $i (1 .. $form->{rowcount} - 1) { + + # account numbers + $pth->execute($form->{"id_$i"}); + $ref = $pth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{"${_}_$i"} = $ref->{$_} } + $pth->finish; + + $projectnumber_id = 0; + $projectnumber = ""; + $form->{partsgroup} = ""; + $form->{projectnumber} = ""; + + if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) { + + $inventory_accno_id = ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) ? "1" : ""; + + if ($form->{groupprojectnumber}) { + ($projectnumber, $projectnumber_id) = split /--/, $form->{"projectnumber_$i"}; + } + if ($form->{grouppartsgroup}) { + ($form->{partsgroup}) = split /--/, $form->{"partsgroup_$i"}; + } + + if ($projectnumber_id && $form->{groupprojectnumber}) { + if ($translation{$projectnumber_id}) { + $form->{projectnumber} = $translation{$projectnumber_id}; + } else { + # get project description + $prh->execute($projectnumber_id); + ($projectdescription, $translation) = $prh->fetchrow_array; + $prh->finish; + + $form->{projectnumber} = ($translation) ? "$projectnumber, $translation" : "$projectnumber, $projectdescription"; + + $translation{$projectnumber_id} = $form->{projectnumber}; + } + } + + if ($form->{grouppartsgroup} && $form->{partsgroup}) { + $form->{projectnumber} .= " / " if $projectnumber_id; + $form->{projectnumber} .= $form->{partsgroup}; + } + + $form->format_string(projectnumber); + + } + + $sortby = qq|$projectnumber$form->{partsgroup}|; + if ($form->{sortby} ne 'runningnumber') { + for (qw(partnumber description bin)) { + $sortby .= $form->{"${_}_$i"} if $form->{sortby} eq $_; + } + } + + push @sortlist, [ $i, qq|$projectnumber$form->{partsgroup}$inventory_accno_id|, $form->{projectnumber}, $projectnumber_id, $form->{partsgroup}, $sortby ]; + + } + + # sort the whole thing by project and group + @sortlist = sort { $a->[5] cmp $b->[5] } @sortlist; + + my $runningnumber = 1; + my $sameitem = ""; + my $subtotal; + my $k = scalar @sortlist; + my $j = 0; + + foreach $item (@sortlist) { + + $i = $item->[0]; + $j++; + + # heading + if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) { + if ($item->[1] ne $sameitem) { + $sameitem = $item->[1]; + + $ok = 0; + + if ($form->{groupprojectnumber}) { + $ok = $form->{"projectnumber_$i"}; + } + if ($form->{grouppartsgroup}) { + $ok = $form->{"partsgroup_$i"} unless $ok; + } + + if ($ok) { + + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, ""); + push(@{ $form->{service} }, NULL); + } else { + push(@{ $form->{part} }, NULL); + push(@{ $form->{service} }, ""); + } + + push(@{ $form->{description} }, $item->[2]); + for (qw(taxrates runningnumber number sku serialnumber bin qty ship unit deliverydate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + } + } + } + + $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}); + + if ($form->{"qty_$i"}) { + + $form->{totalqty} += $form->{"qty_$i"}; + $form->{totalship} += $form->{"qty_$i"}; + $form->{totalweight} += ($form->{"qty_$i"} * $form->{"weight_$i"}); + $form->{totalweightship} += ($form->{"qty_$i"} * $form->{"weight_$i"}); + + # add number, description and qty to $form->{number}, .... + push(@{ $form->{runningnumber} }, $runningnumber++); + push(@{ $form->{number} }, $form->{"partnumber_$i"}); + push(@{ $form->{sku} }, $form->{"sku_$i"}); + push(@{ $form->{serialnumber} }, $form->{"serialnumber_$i"}); + push(@{ $form->{bin} }, $form->{"bin_$i"}); + push(@{ $form->{description} }, $form->{"description_$i"}); + push(@{ $form->{itemnotes} }, $form->{"notes_$i"}); + push(@{ $form->{qty} }, $form->format_amount($myconfig, $form->{"qty_$i"})); + push(@{ $form->{ship} }, $form->format_amount($myconfig, $form->{"qty_$i"})); + push(@{ $form->{unit} }, $form->{"unit_$i"}); + push(@{ $form->{deliverydate} }, $form->{"deliverydate_$i"}); + push(@{ $form->{projectnumber} }, $form->{"projectnumber_$i"}); + + push(@{ $form->{sellprice} }, $form->{"sellprice_$i"}); + + # listprice + push(@{ $form->{listprice} }, $form->{"listprice_$i"}); + + push(@{ $form->{weight} }, $form->format_amount($myconfig, $form->{"weight_$i"} * $form->{"qty_$i"})); + + my $sellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"}); + my ($dec) = ($sellprice =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + my $discount = $form->round_amount($sellprice * $form->parse_amount($myconfig, $form->{"discount_$i"})/100, $decimalplaces); + + # keep a netprice as well, (sellprice - discount) + $form->{"netprice_$i"} = $sellprice - $discount; + + my $linetotal = $form->round_amount($form->{"qty_$i"} * $form->{"netprice_$i"}, 2); + + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, $form->{"sku_$i"}); + push(@{ $form->{service} }, NULL); + $form->{totalparts} += $linetotal; + } else { + push(@{ $form->{service} }, $form->{"sku_$i"}); + push(@{ $form->{part} }, NULL); + $form->{totalservices} += $linetotal; + } + + push(@{ $form->{netprice} }, ($form->{"netprice_$i"}) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : " "); + + $discount = ($discount) ? $form->format_amount($myconfig, $discount * -1, $decimalplaces) : " "; + $linetotal = ($linetotal) ? $linetotal : " "; + + push(@{ $form->{discount} }, $discount); + push(@{ $form->{discountrate} }, $form->format_amount($myconfig, $form->{"discount_$i"})); + + $form->{total} += $linetotal; + + # this is for the subtotals for grouping + $subtotal += $linetotal; + + $form->{"linetotal_$i"} = $form->format_amount($myconfig, $linetotal, 2); + push(@{ $form->{linetotal} }, $form->{"linetotal_$i"}); + + @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + + my $ml = 1; + my @taxrates = (); + + $tax = 0; + + for (0 .. 1) { + $taxrate = 0; + + for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 } + + $taxrate *= $ml; + $taxamount = $linetotal * $taxrate / (1 + $taxrate); + $taxbase = ($linetotal - $taxamount); + + foreach $item (@taxaccounts) { + if (($form->{"${item}_rate"} * $ml) > 0) { + + push @taxrates, $form->{"${item}_rate"} * 100; + + if ($form->{taxincluded}) { + $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"} / (1 + $taxrate); + $taxbase{$item} += $taxbase; + } else { + $taxbase{$item} += $linetotal; + $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"}; + } + } + } + + if ($form->{taxincluded}) { + $tax += $linetotal * ($taxrate / (1 + ($taxrate * $ml))); + } else { + $tax += $linetotal * $taxrate; + } + + $ml *= -1; + } + + push(@{ $form->{lineitems} }, { amount => $linetotal, tax => $form->round_amount($tax, 2) }); + push(@{ $form->{taxrates} }, join ' ', sort { $a <=> $b } @taxrates); + + if ($form->{"assembly_$i"}) { + $form->{stagger} = -1; + &assembly_details($myconfig, $form, $dbh, $form->{"id_$i"}, $oid{$myconfig->{dbdriver}}, $form->{"qty_$i"}); + } + + } + + # add subtotal + if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) { + if ($subtotal) { + if ($j < $k) { + # look at next item + if ($sortlist[$j]->[1] ne $sameitem) { + + if ($form->{"inventory_accno_id_$j"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, ""); + push(@{ $form->{service} }, NULL); + } else { + push(@{ $form->{service} }, ""); + push(@{ $form->{part} }, NULL); + } + + for (qw(taxrates runningnumber number sku serialnumber bin qty ship unit deliverydate projectnumber sellprice listprice netprice discount discountrate weight itemnotes)) { push(@{ $form->{$_} }, "") } + + push(@{ $form->{description} }, $form->{groupsubtotaldescription}); + + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + + if ($form->{groupsubtotaldescription} ne "") { + push(@{ $form->{linetotal} }, $form->format_amount($myconfig, $subtotal, 2)); + } else { + push(@{ $form->{linetotal} }, ""); + } + $subtotal = 0; + } + + } else { + + # got last item + if ($form->{groupsubtotaldescription} ne "") { + + if ($form->{"inventory_accno_id_$j"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, ""); + push(@{ $form->{service} }, NULL); + } else { + push(@{ $form->{service} }, ""); + push(@{ $form->{part} }, NULL); + } + + for (qw(taxrates runningnumber number sku serialnumber bin qty ship unit deliverydate projectnumber sellprice listprice netprice discount discountrate weight itemnotes)) { push(@{ $form->{$_} }, "") } + + push(@{ $form->{description} }, $form->{groupsubtotaldescription}); + push(@{ $form->{linetotal} }, $form->format_amount($myconfig, $subtotal, 2)); + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + } + } + } + } + } + + + $tax = 0; + foreach my $item (sort keys %taxaccounts) { + if ($form->round_amount($taxaccounts{$item}, 2)) { + $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2); + + push(@{ $form->{taxbaseinclusive} }, $form->{"${item}_taxbaseinclusive"} = $form->format_amount($myconfig, $taxbase{$item} + $tax, 2)); + push(@{ $form->{taxbase} }, $form->{"${item}_taxbase"} = $form->format_amount($myconfig, $taxbase{$item}, 2)); + push(@{ $form->{tax} }, $form->{"${item}_tax"} = $form->format_amount($myconfig, $taxamount, 2)); + + push(@{ $form->{taxdescription} }, $form->{"${item}_description"}); + + $form->{"${item}_taxrate"} = $form->format_amount($myconfig, $form->{"${item}_rate"} * 100); + push(@{ $form->{taxrate} }, $form->{"${item}_taxrate"}); + push(@{ $form->{taxnumber} }, $form->{"${item}_taxnumber"}); + } + } + + # adjust taxes for lineitems + my $total = 0; + for (@{ $form->{lineitems} }) { + $total += $_->{tax}; + } + if ($form->round_amount($total,2) != $form->round_amount($tax,2)) { + # get largest amount + for (reverse sort { $a->{tax} <=> $b->{tax} } @{ $form->{lineitems} }) { + $_->{tax} -= $total - $tax; + last; + } + } + $i = 1; + for (@{ $form->{lineitems} }) { + push(@{ $form->{linetax} }, $form->format_amount($myconfig, $_->{tax}, 2, "")); + } + + + for $i (1 .. $form->{paidaccounts}) { + if ($form->{"paid_$i"}) { + push(@{ $form->{payment} }, $form->{"paid_$i"}); + my ($accno, $description) = split /--/, $form->{"AR_paid_$i"}; + push(@{ $form->{paymentaccount} }, $description); + push(@{ $form->{paymentdate} }, $form->{"datepaid_$i"}); + push(@{ $form->{paymentsource} }, $form->{"source_$i"}); + push(@{ $form->{paymentmemo} }, $form->{"memo_$i"}); + + $form->{paid} += $form->parse_amount($myconfig, $form->{"paid_$i"}); + } + } + + for (qw(totalparts totalservices)) { $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) } + for (qw(totalqty totalship totalweight)) { $form->{$_} = $form->format_amount($myconfig, $form->{$_}) } + $form->{subtotal} = $form->format_amount($myconfig, $form->{total}, 2); + $form->{invtotal} = ($form->{taxincluded}) ? $form->{total} : $form->{total} + $tax; + + use LedgerSMB::CP; + my $c; + if ($form->{language_code} ne "") { + $c = new CP $form->{language_code}; + } else { + $c = new CP $myconfig->{countrycode}; + } + $c->init; + my $whole; + ($whole, $form->{decimal}) = split /\./, $form->{invtotal}; + $form->{decimal} .= "00"; + $form->{decimal} = substr($form->{decimal}, 0, 2); + $form->{text_decimal} = $c->num2text($form->{decimal} * 1); + $form->{text_amount} = $c->num2text($whole); + $form->{integer_amount} = $form->format_amount($myconfig, $whole); + + $form->format_string(qw(text_amount text_decimal)); + + $form->{total} = $form->format_amount($myconfig, $form->{invtotal} - $form->{paid}, 2); + $form->{invtotal} = $form->format_amount($myconfig, $form->{invtotal}, 2); + + $form->{paid} = $form->format_amount($myconfig, $form->{paid}, 2); + + $dbh->disconnect; + +} + + +sub assembly_details { + my ($myconfig, $form, $dbh, $id, $oid, $qty) = @_; + + my $sm = ""; + my $spacer; + + $form->{stagger}++; + if ($form->{format} eq 'html') { + $spacer = " " x (3 * ($form->{stagger} - 1)) if $form->{stagger} > 1; + } + if ($form->{format} =~ /(postscript|pdf)/) { + if ($form->{stagger} > 1) { + $spacer = ($form->{stagger} - 1) * 3; + $spacer = '\rule{'.$spacer.'mm}{0mm}'; + } + } + + # get parts and push them onto the stack + my $sortorder = ""; + + if ($form->{grouppartsgroup}) { + $sortorder = qq|ORDER BY pg.partsgroup, a.$oid|; + } else { + $sortorder = qq|ORDER BY a.$oid|; + } + + my $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, + pg.partsgroup, p.partnumber AS sku + FROM assembly a + JOIN parts p ON (a.parts_id = p.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + WHERE a.bom = '1' + AND a.id = '$id' + $sortorder|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + for (qw(partnumber description partsgroup)) { + $form->{"a_$_"} = $ref->{$_}; + $form->format_string("a_$_"); + } + + if ($form->{grouppartsgroup} && $ref->{partsgroup} ne $sm) { + for (qw(taxrates runningnumber number sku serialnumber unit qty ship bin deliverydate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + $sm = ($form->{"a_partsgroup"}) ? $form->{"a_partsgroup"} : "--"; + push(@{ $form->{description} }, "$spacer$sm"); + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + } + + if ($form->{stagger}) { + + push(@{ $form->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq| -- $form->{"a_partnumber"}, $form->{"a_description"}|); + for (qw(taxrates runningnumber number sku serialnumber unit qty ship bin deliverydate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + + } else { + + push(@{ $form->{description} }, qq|$form->{"a_description"}|); + push(@{ $form->{number} }, $form->{"a_partnumber"}); + push(@{ $form->{sku} }, $form->{"a_partnumber"}); + + for (qw(taxrates runningnumber ship serialnumber reqdate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + + } + + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + + push(@{ $form->{qty} }, $form->format_amount($myconfig, $ref->{qty} * $qty)); + + for (qw(unit bin)) { + $form->{"a_$_"} = $ref->{$_}; + $form->format_string("a_$_"); + push(@{ $form->{$_} }, $form->{"a_$_"}); + } + + } + $sth->finish; + + $form->{stagger}--; + +} + + +sub project_description { + my ($self, $dbh, $id) = @_; + + my $query = qq|SELECT description + FROM project + WHERE id = $id|; + ($_) = $dbh->selectrow_array($query); + + $_; + +} + + +sub customer_details { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # get rest for the customer + my $query = qq|SELECT customernumber, name, address1, address2, city, + state, zipcode, country, + contact, phone as customerphone, fax as customerfax, + taxnumber AS customertaxnumber, sic_code AS sic, iban, bic, + startdate, enddate + FROM customer + WHERE id = $form->{customer_id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + $dbh->disconnect; + +} + + +sub post_invoice { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off autocommit + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + my $null; + my $project_id; + my $exchangerate = 0; + my $keepcleared = 0; + + %$form->{acc_trans} = (); + + ($null, $form->{employee_id}) = split /--/, $form->{employee}; + unless ($form->{employee_id}) { + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + } + + ($null, $form->{department_id}) = split(/--/, $form->{department}); + $form->{department_id} *= 1; + + $query = qq|SELECT fxgain_accno_id, fxloss_accno_id + FROM defaults|; + my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query); + + $query = qq|SELECT p.assembly, p.inventory_accno_id, + p.income_accno_id, p.expense_accno_id, p.project_id + FROM parts p + WHERE p.id = ?|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + if ($form->{id}) { + $keepcleared = 1; + $query = qq|SELECT id FROM ar + WHERE id = $form->{id}|; + + if ($dbh->selectrow_array($query)) { + &reverse_invoice($dbh, $form); + } else { + $query = qq|INSERT INTO ar (id) + VALUES ($form->{id})|; + $dbh->do($query) || $form->dberror($query); + } + + } + + my $uid = localtime; + $uid .= "$$"; + + if (! $form->{id}) { + + $query = qq|INSERT INTO ar (invnumber, employee_id) + VALUES ('$uid', $form->{employee_id})|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM ar + WHERE invnumber = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{id}) = $sth->fetchrow_array; + $sth->finish; + } + + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{exchangerate} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, 'buy'); + } + + $form->{exchangerate} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{exchangerate}); + + my $i; + my $item; + my $allocated = 0; + my $taxrate; + my $tax; + my $fxtax; + my @taxaccounts; + my $amount; + my $grossamount; + my $invamount = 0; + my $invnetamount = 0; + my $diff = 0; + my $ml; + my $invoice_id; + my $ndx; + + foreach $i (1 .. $form->{rowcount}) { + $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}); + + if ($form->{"qty_$i"}) { + + $pth->execute($form->{"id_$i"}); + $ref = $pth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{"${_}_$i"} = $ref->{$_} } + $pth->finish; + + # project + $project_id = 'NULL'; + if ($form->{"projectnumber_$i"}) { + ($null, $project_id) = split /--/, $form->{"projectnumber_$i"}; + } + $project_id = $form->{"project_id_$i"} if $form->{"project_id_$i"}; + + # keep entered selling price + my $fxsellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"}); + + my ($dec) = ($fxsellprice =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + # undo discount formatting + $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"})/100; + + # deduct discount + $form->{"sellprice_$i"} = $fxsellprice - $form->round_amount($fxsellprice * $form->{"discount_$i"}, $decimalplaces); + + # linetotal + my $fxlinetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2); + + $amount = $fxlinetotal * $form->{exchangerate}; + my $linetotal = $form->round_amount($amount, 2); + $fxdiff += $amount - $linetotal; + + @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + $ml = 1; + $tax = 0; + $fxtax = 0; + + for (0 .. 1) { + $taxrate = 0; + + # add tax rates + for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 } + + if ($form->{taxincluded}) { + $tax += $amount = $linetotal * ($taxrate / (1 + ($taxrate * $ml))); + $form->{"sellprice_$i"} -= $amount / $form->{"qty_$i"}; + $fxtax += $fxlinetotal * ($taxrate / (1 + ($taxrate * $ml))); + } else { + $tax += $amount = $linetotal * $taxrate; + $fxtax += $fxlinetotal * $taxrate; + } + + for (@taxaccounts) { + $form->{acc_trans}{$form->{id}}{$_}{amount} += $amount * $form->{"${_}_rate"} / $taxrate if ($form->{"${_}_rate"} * $ml) > 0; + } + + $ml = -1; + } + + $grossamount = $form->round_amount($linetotal, 2); + + if ($form->{taxincluded}) { + $amount = $form->round_amount($tax, 2); + $linetotal -= $form->round_amount($tax - $diff, 2); + $diff = ($amount - $tax); + } + + # add linetotal to income + $amount = $form->round_amount($linetotal, 2); + + push @{ $form->{acc_trans}{lineitems} }, { + chart_id => $form->{"income_accno_id_$i"}, + amount => $amount, + fxgrossamount => $fxlinetotal + $fxtax, + grossamount => $grossamount, + project_id => $project_id }; + $ndx = $#{@{$form->{acc_trans}{lineitems}}}; + + $form->{"sellprice_$i"} = $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate}, $decimalplaces); + + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + + if ($form->{"assembly_$i"}) { + # do not update if assembly consists of all services + $query = qq|SELECT sum(p.inventory_accno_id), p.assembly + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + WHERE a.id = $form->{"id_$i"} + GROUP BY p.assembly|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + my ($inv, $assembly) = $sth->fetchrow_array; + $sth->finish; + + if ($inv || $assembly) { + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $form->{"id_$i"}|, + $form->{"qty_$i"} * -1) unless $form->{shipped}; + } + + &process_assembly($dbh, $form, $form->{"id_$i"}, $form->{"qty_$i"}, $project_id); + } else { + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $form->{"id_$i"}|, + $form->{"qty_$i"} * -1) unless $form->{shipped}; + + $allocated = &cogs($dbh, $form, $form->{"id_$i"}, $form->{"qty_$i"}, $project_id); + } + } + + # save detail record in invoice table + $query = qq|INSERT INTO invoice (description) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM invoice + WHERE description = '$uid'|; + ($invoice_id) = $dbh->selectrow_array($query); + + $query = qq|UPDATE invoice SET + trans_id = $form->{id}, + parts_id = $form->{"id_$i"}, + description = |.$dbh->quote($form->{"description_$i"}).qq|, + qty = $form->{"qty_$i"}, + sellprice = $form->{"sellprice_$i"}, + fxsellprice = $fxsellprice, + discount = $form->{"discount_$i"}, + allocated = $allocated, + unit = |.$dbh->quote($form->{"unit_$i"}).qq|, + deliverydate = |.$form->dbquote($form->{"deliverydate_$i"}, SQL_DATE).qq|, + project_id = $project_id, + serialnumber = |.$dbh->quote($form->{"serialnumber_$i"}).qq|, + notes = |.$dbh->quote($form->{"notes_$i"}).qq| + WHERE id = $invoice_id|; + $dbh->do($query) || $form->dberror($query); + + # add invoice_id + $form->{acc_trans}{lineitems}[$ndx]->{invoice_id} = $invoice_id; + + } + } + + $form->{paid} = 0; + for $i (1 .. $form->{paidaccounts}) { + $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"}); + $form->{paid} += $form->{"paid_$i"}; + $form->{datepaid} = $form->{"datepaid_$i"} if ($form->{"paid_$i"}); + } + + # add lineitems + tax + $amount = 0; + $grossamount = 0; + $fxgrossamount = 0; + for (@{ $form->{acc_trans}{lineitems} }) { + $amount += $_->{amount}; + $grossamount += $_->{grossamount}; + $fxgrossamount += $_->{fxgrossamount}; + } + $invnetamount = $amount; + + $amount = 0; + for (split / /, $form->{taxaccounts}) { $amount += $form->{acc_trans}{$form->{id}}{$_}{amount} = $form->round_amount($form->{acc_trans}{$form->{id}}{$_}{amount}, 2) } + $invamount = $invnetamount + $amount; + + $diff = 0; + if ($form->{taxincluded}) { + $diff = $form->round_amount($grossamount - $invamount, 2); + $invamount += $diff; + } + $fxdiff = $form->round_amount($fxdiff,2); + $invnetamount += $fxdiff; + $invamount += $fxdiff; + + if ($form->round_amount($form->{paid} - $fxgrossamount,2) == 0) { + $form->{paid} = $invamount; + } else { + $form->{paid} = $form->round_amount($form->{paid} * $form->{exchangerate}, 2); + } + + foreach $ref (sort { $b->{amount} <=> $a->{amount} } @ { $form->{acc_trans}{lineitems} }) { + $amount = $ref->{amount} + $diff + $fxdiff; + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, project_id, invoice_id) + VALUES ($form->{id}, $ref->{chart_id}, $amount, + '$form->{transdate}', $ref->{project_id}, $ref->{invoice_id})|; + $dbh->do($query) || $form->dberror($query); + $diff = 0; + $fxdiff = 0; + } + + $form->{receivables} = $invamount * -1; + + delete $form->{acc_trans}{lineitems}; + + # update exchangerate + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0); + } + + # record receivable + if ($form->{receivables}) { + ($accno) = split /--/, $form->{AR}; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate) + VALUES ($form->{id}, + (SELECT id FROM chart + WHERE accno = '$accno'), + $form->{receivables}, '$form->{transdate}')|; + $dbh->do($query) || $form->dberror($query); + } + + foreach my $trans_id (keys %{$form->{acc_trans}}) { + foreach my $accno (keys %{$form->{acc_trans}{$trans_id}}) { + $amount = $form->round_amount($form->{acc_trans}{$trans_id}{$accno}{amount}, 2); + if ($amount) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate) + VALUES ($trans_id, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount, '$form->{transdate}')|; + $dbh->do($query) || $form->dberror($query); + } + } + } + + + # if there is no amount but a payment record receivable + if ($invamount == 0) { + $form->{receivables} = 1; + } + + my $cleared = 0; + + # record payments and offsetting AR + for $i (1 .. $form->{paidaccounts}) { + + if ($form->{"paid_$i"}) { + my ($accno) = split /--/, $form->{"AR_paid_$i"}; + $form->{"datepaid_$i"} = $form->{transdate} unless ($form->{"datepaid_$i"}); + $form->{datepaid} = $form->{"datepaid_$i"}; + + $exchangerate = 0; + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{"exchangerate_$i"} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy'); + + $form->{"exchangerate_$i"} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{"exchangerate_$i"}); + } + + + # record AR + $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2); + + if ($form->{receivables}) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$form->{AR}'), + $amount, '$form->{"datepaid_$i"}')|; + $dbh->do($query) || $form->dberror($query); + } + + # record payment + $amount = $form->{"paid_$i"} * -1; + if ($keepcleared) { + $cleared = ($form->{"cleared_$i"}) ? 1 : 0; + } + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, + source, memo, cleared) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount, '$form->{"datepaid_$i"}', | + .$dbh->quote($form->{"source_$i"}).qq|, | + .$dbh->quote($form->{"memo_$i"}).qq|, '$cleared')|; + $dbh->do($query) || $form->dberror($query); + + # exchangerate difference + $amount = $form->round_amount(($form->round_amount($form->{"paid_$i"} * $form->{"exchangerate_$i"} - $form->{"paid_$i"}, 2)) * -1, 2); + + if ($amount) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, source, fx_transaction, cleared) + VALUES ($form->{id}, (SELECT id FROM chart + WHERE accno = '$accno'), + $amount, '$form->{"datepaid_$i"}', | + .$dbh->quote($form->{"source_$i"}).qq|, '1', '$cleared')|; + $dbh->do($query) || $form->dberror($query); + } + + # gain/loss + $amount = $form->round_amount(($form->round_amount($form->{"paid_$i"} * $form->{exchangerate},2) - $form->round_amount($form->{"paid_$i"} * $form->{"exchangerate_$i"},2)) * -1, 2); + + if ($amount) { + my $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id; + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, + transdate, fx_transaction, cleared) + VALUES ($form->{id}, $accno_id, + $amount, '$form->{"datepaid_$i"}', '1', '$cleared')|; + $dbh->do($query) || $form->dberror($query); + } + + # update exchange rate + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + $form->update_exchangerate($dbh, $form->{currency}, $form->{"datepaid_$i"}, $form->{"exchangerate_$i"}, 0); + } + } + } + + # set values which could be empty to 0 + $form->{terms} *= 1; + $form->{taxincluded} *= 1; + + # if this is from a till + my $till = ($form->{till}) ? qq|'$form->{till}'| : "NULL"; + + $form->{invnumber} = $form->update_defaults($myconfig, "sinumber", $dbh) unless $form->{invnumber}; + + # save AR record + $query = qq|UPDATE ar set + invnumber = |.$dbh->quote($form->{invnumber}).qq|, + ordnumber = |.$dbh->quote($form->{ordnumber}).qq|, + quonumber = |.$dbh->quote($form->{quonumber}).qq|, + transdate = '$form->{transdate}', + customer_id = $form->{customer_id}, + amount = $invamount, + netamount = $invnetamount, + paid = $form->{paid}, + datepaid = |.$form->dbquote($form->{datepaid}, SQL_DATE).qq|, + duedate = |.$form->dbquote($form->{duedate}, SQL_DATE).qq|, + invoice = '1', + shippingpoint = |.$dbh->quote($form->{shippingpoint}).qq|, + shipvia = |.$dbh->quote($form->{shipvia}).qq|, + terms = $form->{terms}, + notes = |.$dbh->quote($form->{notes}).qq|, + intnotes = |.$dbh->quote($form->{intnotes}).qq|, + taxincluded = '$form->{taxincluded}', + curr = '$form->{currency}', + department_id = $form->{department_id}, + employee_id = $form->{employee_id}, + till = $till, + language_code = '$form->{language_code}', + ponumber = |.$dbh->quote($form->{ponumber}).qq| + WHERE id = $form->{id} + |; + $dbh->do($query) || $form->dberror($query); + + # add shipto + $form->{name} = $form->{customer}; + $form->{name} =~ s/--$form->{customer_id}//; + $form->add_shipto($dbh, $form->{id}); + + # save printed, emailed and queued + $form->save_status($dbh); + + my %audittrail = ( tablename => 'ar', + reference => $form->{invnumber}, + formname => $form->{type}, + action => 'posted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + $form->save_recurring($dbh, $myconfig); + + my $rc = $dbh->commit; + + $dbh->disconnect; + + $rc; + +} + + +sub process_assembly { + my ($dbh, $form, $id, $totalqty, $project_id) = @_; + + my $query = qq|SELECT a.parts_id, a.qty, p.assembly, + p.partnumber, p.description, p.unit, + p.inventory_accno_id, p.income_accno_id, + p.expense_accno_id + FROM assembly a + JOIN parts p ON (a.parts_id = p.id) + WHERE a.id = $id|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $allocated; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + $allocated = 0; + + $ref->{inventory_accno_id} *= 1; + $ref->{expense_accno_id} *= 1; + + # multiply by number of assemblies + $ref->{qty} *= $totalqty; + + if ($ref->{assembly}) { + &process_assembly($dbh, $form, $ref->{parts_id}, $ref->{qty}, $project_id); + next; + } else { + if ($ref->{inventory_accno_id}) { + $allocated = &cogs($dbh, $form, $ref->{parts_id}, $ref->{qty}, $project_id); + } + } + + # save detail record for individual assembly item in invoice table + $query = qq|INSERT INTO invoice (trans_id, description, parts_id, qty, + sellprice, fxsellprice, allocated, assemblyitem, unit) + VALUES + ($form->{id}, | + .$dbh->quote($ref->{description}).qq|, + $ref->{parts_id}, $ref->{qty}, 0, 0, $allocated, 't', | + .$dbh->quote($ref->{unit}).qq|)|; + $dbh->do($query) || $form->dberror($query); + + } + + $sth->finish; + +} + + +sub cogs { + my ($dbh, $form, $id, $totalqty, $project_id) = @_; + + my $query = qq|SELECT i.id, i.trans_id, i.qty, i.allocated, i.sellprice, + i.fxsellprice, p.inventory_accno_id, p.expense_accno_id + FROM invoice i, parts p + WHERE i.parts_id = p.id + AND i.parts_id = $id + AND (i.qty + i.allocated) < 0 + ORDER BY trans_id|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $allocated = 0; + my $qty; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + if (($qty = (($ref->{qty} * -1) - $ref->{allocated})) > $totalqty) { + $qty = $totalqty; + } + + $form->update_balance($dbh, + "invoice", + "allocated", + qq|id = $ref->{id}|, + $qty); + + # total expenses and inventory + # sellprice is the cost of the item + my $linetotal = $form->round_amount($ref->{sellprice} * $qty, 2); + + # add expense + push @{ $form->{acc_trans}{lineitems} }, { + chart_id => $ref->{expense_accno_id}, + amount => $linetotal * -1, + project_id => $project_id, + invoice_id => $ref->{id} }; + + # deduct inventory + push @{ $form->{acc_trans}{lineitems} }, { + chart_id => $ref->{inventory_accno_id}, + amount => $linetotal, + project_id => $project_id, + invoice_id => $ref->{id} }; + + # add allocated + $allocated += -$qty; + + last if (($totalqty -= $qty) <= 0); + } + + $sth->finish; + + $allocated; + +} + + + +sub reverse_invoice { + my ($dbh, $form) = @_; + + my $query = qq|SELECT id FROM ar + WHERE id = $form->{id}|; + my ($id) = $dbh->selectrow_array($query); + + return unless $id; + + # reverse inventory items + my $query = qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, + p.inventory_accno_id + FROM invoice i + JOIN parts p ON (i.parts_id = p.id) + WHERE i.trans_id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + if ($ref->{inventory_accno_id} || $ref->{assembly}) { + + # if the invoice item is not an assemblyitem adjust parts onhand + if (!$ref->{assemblyitem}) { + # adjust onhand in parts table + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $ref->{parts_id}|, + $ref->{qty}); + } + + # loop if it is an assembly + next if ($ref->{assembly}); + + # de-allocated purchases + $query = qq|SELECT id, trans_id, allocated + FROM invoice + WHERE parts_id = $ref->{parts_id} + AND allocated > 0 + ORDER BY trans_id DESC|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $inhref = $sth->fetchrow_hashref(NAME_lc)) { + $qty = $ref->{qty}; + if (($ref->{qty} - $inhref->{allocated}) > 0) { + $qty = $inhref->{allocated}; + } + + # update invoice + $form->update_balance($dbh, + "invoice", + "allocated", + qq|id = $inhref->{id}|, + $qty * -1); + + last if (($ref->{qty} -= $qty) <= 0); + } + $sth->finish; + } + } + + $sth->finish; + + # delete acc_trans + $query = qq|DELETE FROM acc_trans + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete invoice entries + $query = qq|DELETE FROM invoice + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM shipto + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $dbh->commit; + +} + + + +sub delete_invoice { + my ($self, $myconfig, $form, $spool) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + &reverse_invoice($dbh, $form); + + my %audittrail = ( tablename => 'ar', + reference => $form->{invnumber}, + formname => $form->{type}, + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + # delete AR record + my $query = qq|DELETE FROM ar + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete spool files + $query = qq|SELECT spoolfile FROM status + WHERE trans_id = $form->{id} + AND spoolfile IS NOT NULL|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $spoolfile; + my @spoolfiles = (); + + while (($spoolfile) = $sth->fetchrow_array) { + push @spoolfiles, $spoolfile; + } + $sth->finish; + + # delete status entries + $query = qq|DELETE FROM status + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + + if ($rc) { + foreach $spoolfile (@spoolfiles) { + unlink "$spool/$spoolfile" if $spoolfile; + } + } + + $dbh->disconnect; + + $rc; + +} + + + +sub retrieve_invoice { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + + if ($form->{id}) { + # get default accounts and last invoice number + $query = qq|SELECT d.curr AS currencies + FROM defaults d|; + } else { + $query = qq|SELECT d.curr AS currencies, current_date AS transdate + FROM defaults d|; + } + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + + if ($form->{id}) { + + # retrieve invoice + $query = qq|SELECT a.invnumber, a.ordnumber, a.quonumber, + a.transdate, a.paid, + a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, + a.duedate, a.taxincluded, a.curr AS currency, + a.employee_id, e.name AS employee, a.till, a.customer_id, + a.language_code, a.ponumber + FROM ar a + LEFT JOIN employee e ON (e.id = a.employee_id) + WHERE a.id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # get shipto + $query = qq|SELECT * FROM shipto + WHERE trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # retrieve individual items + $query = qq|SELECT i.description, i.qty, i.fxsellprice, i.sellprice, + i.discount, i.parts_id AS id, i.unit, i.deliverydate, + i.project_id, pr.projectnumber, i.serialnumber, i.notes, + p.partnumber, p.assembly, p.bin, + pg.partsgroup, p.partsgroup_id, p.partnumber AS sku, + p.listprice, p.lastcost, p.weight, p.onhand, + p.inventory_accno_id, p.income_accno_id, p.expense_accno_id, + t.description AS partsgrouptranslation + FROM invoice i + JOIN parts p ON (i.parts_id = p.id) + LEFT JOIN project pr ON (i.project_id = pr.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN translation t ON (t.trans_id = p.partsgroup_id AND t.language_code = '$form->{language_code}') + WHERE i.trans_id = $form->{id} + AND NOT i.assemblyitem = '1' + ORDER BY i.id|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # foreign currency + &exchangerate_defaults($dbh, $form); + + # query for price matrix + my $pmh = &price_matrix_query($dbh, $form); + + # taxes + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (pt.chart_id = c.id) + WHERE pt.parts_id = ?|; + my $tth = $dbh->prepare($query) || $form->dberror($query); + + my $taxrate; + my $ptref; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + my ($dec) = ($ref->{fxsellprice} =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + $tth->execute($ref->{id}); + + $ref->{taxaccounts} = ""; + $taxrate = 0; + + while ($ptref = $tth->fetchrow_hashref(NAME_lc)) { + $ref->{taxaccounts} .= "$ptref->{accno} "; + $taxrate += $form->{"$ptref->{accno}_rate"}; + } + $tth->finish; + chop $ref->{taxaccounts}; + + # price matrix + $ref->{sellprice} = ($ref->{fxsellprice} * $form->{$form->{currency}}); + &price_matrix($pmh, $ref, $form->{transdate}, $decimalplaces, $form, $myconfig); + $ref->{sellprice} = $ref->{fxsellprice}; + + $ref->{partsgroup} = $ref->{partsgrouptranslation} if $ref->{partsgrouptranslation}; + + push @{ $form->{invoice_details} }, $ref; + } + $sth->finish; + + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub retrieve_item { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $i = $form->{rowcount}; + my $null; + my $var; + + my $where = "WHERE p.obsolete = '0' AND NOT p.income_accno_id IS NULL"; + + if ($form->{"partnumber_$i"} ne "") { + $var = $form->like(lc $form->{"partnumber_$i"}); + $where .= " AND lower(p.partnumber) LIKE '$var'"; + } + if ($form->{"description_$i"} ne "") { + $var = $form->like(lc $form->{"description_$i"}); + if ($form->{language_code} ne "") { + $where .= " AND lower(t1.description) LIKE '$var'"; + } else { + $where .= " AND lower(p.description) LIKE '$var'"; + } + } + + if ($form->{"partsgroup_$i"} ne "") { + ($null, $var) = split /--/, $form->{"partsgroup_$i"}; + $var *= 1; + if ($var == 0) { + # search by partsgroup, this is for the POS + $where .= qq| AND pg.partsgroup = '$form->{"partsgroup_$i"}'|; + } else { + $where .= qq| AND p.partsgroup_id = $var|; + } + } + + if ($form->{"description_$i"} ne "") { + $where .= " ORDER BY 3"; + } else { + $where .= " ORDER BY 2"; + } + + my $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, + p.listprice, p.lastcost, + p.unit, p.assembly, p.bin, p.onhand, p.notes, + p.inventory_accno_id, p.income_accno_id, p.expense_accno_id, + pg.partsgroup, p.partsgroup_id, p.partnumber AS sku, + p.weight, + t1.description AS translation, + t2.description AS grouptranslation + FROM parts p + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + LEFT JOIN translation t1 ON (t1.trans_id = p.id AND t1.language_code = '$form->{language_code}') + LEFT JOIN translation t2 ON (t2.trans_id = p.partsgroup_id AND t2.language_code = '$form->{language_code}') + $where|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref; + my $ptref; + + # setup exchange rates + &exchangerate_defaults($dbh, $form); + + # taxes + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (c.id = pt.chart_id) + WHERE pt.parts_id = ?|; + my $tth = $dbh->prepare($query) || $form->dberror($query); + + + # price matrix + my $pmh = &price_matrix_query($dbh, $form); + + my $transdate = $form->datetonum($myconfig, $form->{transdate}); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + my ($dec) = ($ref->{sellprice} =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + # get taxes for part + $tth->execute($ref->{id}); + + $ref->{taxaccounts} = ""; + while ($ptref = $tth->fetchrow_hashref(NAME_lc)) { + $ref->{taxaccounts} .= "$ptref->{accno} "; + } + $tth->finish; + chop $ref->{taxaccounts}; + + # get matrix + &price_matrix($pmh, $ref, $transdate, $decimalplaces, $form, $myconfig); + + $ref->{description} = $ref->{translation} if $ref->{translation}; + $ref->{partsgroup} = $ref->{grouptranslation} if $ref->{grouptranslation}; + + push @{ $form->{item_list} }, $ref; + + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub price_matrix_query { + my ($dbh, $form) = @_; + + my $query = qq|SELECT p.id AS parts_id, 0 AS customer_id, 0 AS pricegroup_id, + 0 AS pricebreak, p.sellprice, NULL AS validfrom, NULL AS validto, + '$form->{defaultcurrency}' AS curr, '' AS pricegroup + FROM parts p + WHERE p.id = ? + + UNION + + SELECT p.*, g.pricegroup + FROM partscustomer p + LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id) + WHERE p.parts_id = ? + AND p.customer_id = $form->{customer_id} + + UNION + + SELECT p.*, g.pricegroup + FROM partscustomer p + LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id) + JOIN customer c ON (c.pricegroup_id = g.id) + WHERE p.parts_id = ? + AND c.id = $form->{customer_id} + + UNION + + SELECT p.*, '' AS pricegroup + FROM partscustomer p + WHERE p.customer_id = 0 + AND p.pricegroup_id = 0 + AND p.parts_id = ? + + ORDER BY customer_id DESC, pricegroup_id DESC, pricebreak + + |; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + $sth; + +} + + +sub price_matrix { + my ($pmh, $ref, $transdate, $decimalplaces, $form, $myconfig) = @_; + + $pmh->execute($ref->{id}, $ref->{id}, $ref->{id}, $ref->{id}); + + $ref->{pricematrix} = ""; + + my $customerprice; + my $pricegroupprice; + my $sellprice; + my $baseprice; + my $mref; + my %p = (); + my $i = 0; + + while ($mref = $pmh->fetchrow_hashref(NAME_lc)) { + + # check date + if ($mref->{validfrom}) { + next if $transdate < $form->datetonum($myconfig, $mref->{validfrom}); + } + if ($mref->{validto}) { + next if $transdate > $form->datetonum($myconfig, $mref->{validto}); + } + + # convert price + $sellprice = $form->round_amount($mref->{sellprice} * $form->{$mref->{curr}}, $decimalplaces); + + $mref->{pricebreak} *= 1; + + if ($mref->{customer_id}) { + $p{$mref->{pricebreak}} = $sellprice; + $customerprice = 1; + } + + if ($mref->{pricegroup_id}) { + if (!$customerprice) { + $p{$mref->{pricebreak}} = $sellprice; + $pricegroupprice = 1; + } + } + + if (!$customerprice && !$pricegroupprice) { + $p{$mref->{pricebreak}} = $sellprice; + } + + if (($mref->{pricebreak} + $mref->{customer_id} + $mref->{pricegroup_id}) == 0) { + $baseprice = $sellprice; + } + + $i++; + + } + $pmh->finish; + + if (! exists $p{0}) { + $p{0} = $baseprice; + } + + if ($i > 1) { + $ref->{sellprice} = $p{0}; + for (sort { $a <=> $b } keys %p) { $ref->{pricematrix} .= "${_}:$p{$_} " } + } else { + $ref->{sellprice} = $form->round_amount($p{0} * (1 - $form->{tradediscount}), $decimalplaces); + $ref->{pricematrix} = "0:$ref->{sellprice} " if $ref->{sellprice}; + } + chop $ref->{pricematrix}; + +} + + +sub exchangerate_defaults { + my ($dbh, $form) = @_; + + my $var; + + # get default currencies + my $query = qq|SELECT substr(curr,1,3), curr FROM defaults|; + my $eth = $dbh->prepare($query) || $form->dberror($query); + $eth->execute; + ($form->{defaultcurrency}, $form->{currencies}) = $eth->fetchrow_array; + $eth->finish; + + $query = qq|SELECT buy + FROM exchangerate + WHERE curr = ? + AND transdate = ?|; + my $eth1 = $dbh->prepare($query) || $form->dberror($query); + + $query = qq~SELECT max(transdate || ' ' || buy || ' ' || curr) + FROM exchangerate + WHERE curr = ?~; + my $eth2 = $dbh->prepare($query) || $form->dberror($query); + + # get exchange rates for transdate or max + foreach $var (split /:/, substr($form->{currencies},4)) { + $eth1->execute($var, $form->{transdate}); + ($form->{$var}) = $eth1->fetchrow_array; + if (! $form->{$var} ) { + $eth2->execute($var); + + ($form->{$var}) = $eth2->fetchrow_array; + ($null, $form->{$var}) = split / /, $form->{$var}; + $form->{$var} = 1 unless $form->{$var}; + $eth2->finish; + } + $eth1->finish; + } + + $form->{$form->{currency}} = $form->{exchangerate} if $form->{exchangerate}; + $form->{$form->{currency}} ||= 1; + $form->{$form->{defaultcurrency}} = 1; + +} + + +1; + diff --git a/LedgerSMB/Inifile.pm b/LedgerSMB/Inifile.pm new file mode 100755 index 00000000..0b5055df --- /dev/null +++ b/LedgerSMB/Inifile.pm @@ -0,0 +1,74 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# routines to retrieve / manipulate win ini style files +# ORDER is used to keep the elements in the order they appear in .ini +# +#===================================================================== + +package Inifile; + + +sub new { + my ($type, $file) = @_; + + warn "$type has no copy constructor! creating a new object." if ref($type); + $type = ref($type) || $type; + my $self = bless {}, $type; + $self->add_file($file) if defined $file; + + return $self; +} + + +sub add_file { + my ($self, $file) = @_; + + my $id = ""; + my %menuorder = (); + + for (@{$self->{ORDER}}) { $menuorder{$_} = 1 } + + open FH, "$file" or Form->error("$file : $!"); + + while (<FH>) { + next if /^(#|;|\s)/; + last if /^\./; + + chop; + + # strip comments + s/\s*(#|;).*//g; + + # remove any trailing whitespace + s/^\s*(.*?)\s*$/$1/; + + if (/^\[/) { + s/(\[|\])//g; + $id = $_; + push @{$self->{ORDER}}, $_ if ! $menuorder{$_}; + $menuorder{$_} = 1; + next; + } + + # add key=value to $id + my ($key, $value) = split /=/, $_, 2; + + $self->{$id}{$key} = $value; + + } + close FH; + +} + + +1; + diff --git a/LedgerSMB/JC.pm b/LedgerSMB/JC.pm new file mode 100755 index 00000000..af1ffd40 --- /dev/null +++ b/LedgerSMB/JC.pm @@ -0,0 +1,582 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Job Costing +# +#====================================================================== + + +package JC; + +use LedgerSMB::IS; + +sub get_jcitems { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT current_date FROM defaults|; + ($form->{transdate}) = $dbh->selectrow_array($query); + + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + + my $dateformat = $myconfig->{dateformat}; + $dateformat =~ s/yy/yyyy/; + $dateformat =~ s/yyyyyy/yyyy/; + + if ($form->{id}) { + # retrieve timecard/storescard + $query = qq|SELECT j.*, to_char(j.checkedin, 'HH24:MI:SS') AS checkedina, + to_char(j.checkedout, 'HH24:MI:SS') AS checkedouta, + to_char(j.checkedin, '$dateformat') AS transdate, + e.name AS employee, p.partnumber, + pr.projectnumber, pr.description AS projectdescription, + pr.production, pr.completed, pr.parts_id AS project + FROM jcitems j + JOIN employee e ON (e.id = j.employee_id) + JOIN parts p ON (p.id = j.parts_id) + JOIN project pr ON (pr.id = j.project_id) + WHERE j.id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + $form->{project} = ($form->{project}) ? "job" : "project"; + for (qw(checkedin checkedout)) { + $form->{$_} = $form->{"${_}a"}; + delete $form->{"${_}a"}; + } + + $query = qq|SELECT s.printed, s.spoolfile, s.formname + FROM status s + WHERE s.formname = '$form->{type}' + AND s.trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{printed} .= "$ref->{formname} " if $ref->{printed}; + $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile}; + } + $sth->finish; + for (qw(printed queued)) { $form->{$_} =~ s/ +$//g } + } + + JC->jcitems_links($myconfig, $form, $dbh); + + # get language codes + $query = qq|SELECT * + FROM language + ORDER BY 2|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $form->{all_language} = (); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_language} }, $ref; + } + $sth->finish; + + $dbh->disconnect; + +} + + +sub jcitems_links { + my ($self, $myconfig, $form, $dbh) = @_; + + my $disconnect = 0; + + if (! $dbh) { + $dbh = $form->dbconnect($myconfig); + $disconnect = 1; + } + + my $query; + + if ($form->{project_id}) { + $form->{orphaned} = 1; + $query = qq|SELECT parts_id + FROM project + WHERE id = $form->{project_id}|; + if ($dbh->selectrow_array($query)) { + $form->{project} = 'job'; + $query = qq|SELECT id + FROM project + WHERE parts_id > 0 + AND production > completed + AND id = $form->{project_id}|; + ($form->{orphaned}) = $dbh->selectrow_array($q); + } else { + $form->{project} = 'project'; + } + } + + JC->jcparts($myconfig, $form, $dbh); + + $form->all_employees($myconfig, $dbh, $form->{transdate}); + + my $where; + + if ($form->{transdate}) { + $where .= qq| AND (enddate IS NULL + OR enddate >= '$form->{transdate}') + AND (startdate <= '$form->{transdate}' + OR startdate IS NULL)|; + } + + if ($form->{project} eq 'job') { + $query = qq| + SELECT pr.* + FROM project pr + WHERE pr.parts_id > 0 + AND pr.production > pr.completed + $where|; + } elsif ($form->{project} eq 'project') { + $query = qq| + SELECT pr.* + FROM project pr + WHERE pr.parts_id IS NULL + $where|; + } else { + $query = qq| + SELECT pr.* + FROM project pr + WHERE 1=1 + $where + EXCEPT + SELECT pr.* + FROM project pr + WHERE pr.parts_id > 0 + AND pr.production = pr.completed|; + } + + if ($form->{project_id}) { + $query .= qq| + UNION + SELECT * + FROM project + WHERE id = $form->{project_id}|; + } + + $query .= qq| + ORDER BY projectnumber|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_project} }, $ref; + } + $sth->finish; + + $dbh->disconnect if $disconnect; + +} + + +sub jcparts { + my ($self, $myconfig, $form, $dbh) = @_; + + my ($null, $project_id) = split /--/, $form->{projectnumber}; + $project_id *= 1; + + my $query = qq|SELECT customer_id + FROM project + WHERE id = $project_id|; + my ($customer_id) = $dbh->selectrow_array($query); + $customer_id *= 1; + + my $where; + + if ($form->{project} eq 'job') { + $where = " AND p.income_accno_id IS NULL"; + if ($form->{type} eq 'storescard') { + $where = " AND p.inventory_accno_id > 0 + AND p.income_accno_id > 0"; + } + + $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, + p.unit, t.description AS translation + FROM parts p + LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}') + WHERE p.obsolete = '0' + $where|; + } elsif ($form->{project} eq 'project') { + $where = " AND p.inventory_accno_id IS NULL"; + if ($form->{type} eq 'storescard') { + $where = " AND p.inventory_accno_id > 0"; + } + + $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, + p.unit, t.description AS translation + FROM parts p + LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}') + WHERE p.obsolete = '0' + AND p.assembly = '0' + $where|; + } else { + + $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, + p.unit, t.description AS translation + FROM parts p + LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}') + WHERE p.obsolete = '0' + AND p.income_accno_id IS NULL + UNION + SELECT p.id, p.partnumber, p.description, p.sellprice, + p.unit, t.description AS translation + FROM parts p + LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}') + WHERE p.obsolete = '0' + AND p.assembly = '0' + AND p.inventory_accno_id IS NULL|; + } + + $query .= qq| + ORDER BY 2|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $pmh = price_matrix_query($dbh, $project_id, $customer_id); + IS::exchangerate_defaults($dbh, $form); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{description} = $ref->{translation} if $ref->{translation}; + IS::price_matrix($pmh, $ref, $form->datetonum($form->{transdate}), 4, $form, $myconfig); + push @{ $form->{all_parts} }, $ref; + } + $sth->finish; + +} + + +sub delete_timecard { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my %audittrail = ( tablename => 'jcitems', + reference => $form->{id}, + formname => $form->{type}, + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $query = qq|DELETE FROM jcitems + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete spool files + $query = qq|SELECT spoolfile FROM status + WHERE formname = '$form->{type}' + AND trans_id = $form->{id} + AND spoolfile IS NOT NULL|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $spoolfile; + my @spoolfiles = (); + + while (($spoolfile) = $sth->fetchrow_array) { + push @spoolfiles, $spoolfile; + } + $sth->finish; + + # delete status entries + $query = qq|DELETE FROM status + WHERE formname = '$form->{type}' + AND trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + + if ($rc) { + foreach $spoolfile (@spoolfiles) { + unlink "$spool/$spoolfile" if $spoolfile; + } + } + + $dbh->disconnect; + + $rc; + +} + + +sub jcitems { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $where = "1 = 1"; + my $null; + my $var; + + if ($form->{projectnumber}) { + ($null, $var) = split /--/, $form->{projectnumber}; + $where .= " AND j.project_id = $var"; + + $query = qq|SELECT parts_id + FROM project + WHERE id = $var|; + my ($job) = $dbh->selectrow_array($query); + $form->{project} = ($job) ? "job" : "project"; + + } + if ($form->{partnumber}) { + ($null, $var) = split /--/, $form->{partnumber}; + $where .= " AND j.parts_id = $var"; + + $query = qq|SELECT inventory_accno_id + FROM parts + WHERE id = $var|; + my ($job) = $dbh->selectrow_array($query); + $form->{project} = ($job) ? "job" : "project"; + + } + if ($form->{employee}) { + ($null, $var) = split /--/, $form->{employee}; + $where .= " AND j.employee_id = $var"; + } + if ($form->{open} || $form->{closed}) { + unless ($form->{open} && $form->{closed}) { + $where .= " AND j.qty != j.allocated" if $form->{open}; + $where .= " AND j.qty = j.allocated" if $form->{closed}; + } + } + + ($form->{startdatefrom}, $form->{startdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + $where .= " AND j.checkedin >= '$form->{startdatefrom}'" if $form->{startdatefrom}; + $where .= " AND j.checkedout < date '$form->{startdateto}' + 1" if $form->{startdateto}; + + my %ordinal = ( id => 1, + description => 2, + transdate => 7, + partnumber => 9, + projectnumber => 10, + projectdescription => 11, + ); + + my @a = (transdate, projectnumber); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $dateformat = $myconfig->{dateformat}; + $dateformat =~ s/yy$/yyyy/; + $dateformat =~ s/yyyyyy/yyyy/; + + if ($form->{project} eq 'job') { + if ($form->{type} eq 'timecard') { + $where .= " AND pr.parts_id > 0 + AND p.income_accno_id IS NULL"; + } + + if ($form->{type} eq 'storescard') { + $where .= " AND pr.parts_id > 0 + AND p.income_accno_id > 0"; + } + } + if ($form->{project} eq 'project') { + $where .= " AND pr.parts_id IS NULL"; + } + + $query = qq|SELECT j.id, j.description, j.qty, j.allocated, + to_char(j.checkedin, 'HH24:MI') AS checkedin, + to_char(j.checkedout, 'HH24:MI') AS checkedout, + to_char(j.checkedin, 'yyyymmdd') AS transdate, + to_char(j.checkedin, '$dateformat') AS transdatea, + to_char(j.checkedin, 'D') AS weekday, + p.partnumber, + pr.projectnumber, pr.description AS projectdescription, + e.employeenumber, e.name AS employee, + to_char(j.checkedin, 'WW') AS workweek, pr.parts_id, + j.sellprice + FROM jcitems j + JOIN parts p ON (p.id = j.parts_id) + JOIN project pr ON (pr.id = j.project_id) + JOIN employee e ON (e.id = j.employee_id) + WHERE $where + ORDER BY employee, employeenumber, $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{project} = ($ref->{parts_id}) ? "job" : "project"; + $ref->{transdate} = $ref->{transdatea}; + delete $ref->{transdatea}; + push @{ $form->{transactions} }, $ref; + } + $sth->finish; + + $dbh->disconnect; + +} + + +sub save { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + + my ($null, $project_id) = split /--/, $form->{projectnumber}; + + if ($form->{id}) { + # check if it was a job + $query = qq|SELECT pr.parts_id, pr.production - pr.completed + FROM project pr + JOIN jcitems j ON (j.project_id = pr.id) + WHERE j.id = $form->{id}|; + my ($job_id, $qty) = $dbh->selectrow_array($query); + + if ($job_id && $qty == 0) { + $dbh->disconnect; + return -1; + } + + # check if new one belongs to a job + if ($project_id) { + $query = qq|SELECT pr.parts_id, pr.production - pr.completed + FROM project pr + WHERE pr.id = $project_id|; + my ($job_id, $qty) = $dbh->selectrow_array($query); + + if ($job_id && $qty == 0) { + $dbh->disconnect; + return -2; + } + } + + } else { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO jcitems (description) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM jcitems + WHERE description = '$uid'|; + ($form->{id}) = $dbh->selectrow_array($query); + } + + for (qw(inhour inmin insec outhour outmin outsec)) { $form->{$_} = substr("00$form->{$_}", -2) } + for (qw(qty sellprice allocated)) { $form->{$_} = $form->parse_amount($myconfig, $form->{$_}) } + + my $checkedin = "$form->{inhour}$form->{inmin}$form->{insec}"; + my $checkedout = "$form->{outhour}$form->{outmin}$form->{outsec}"; + + my $outdate = $form->{transdate}; + if ($checkedout < $checkedin) { + $outdate = $form->add_date($myconfig, $form->{transdate}, 1, 'days'); + } + + ($null, $form->{employee_id}) = split /--/, $form->{employee}; + unless ($form->{employee_id}) { + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + } + + my $parts_id; + ($null, $parts_id) = split /--/, $form->{partnumber}; + + $query = qq|UPDATE jcitems SET + project_id = $project_id, + parts_id = $parts_id, + description = |.$dbh->quote($form->{description}).qq|, + qty = $form->{qty}, + allocated = $form->{allocated}, + sellprice = $form->{sellprice}, + fxsellprice = $form->{sellprice}, + serialnumber = |.$dbh->quote($form->{serialnumber}).qq|, + checkedin = timestamp '$form->{transdate} $form->{inhour}:$form->{inmin}:$form->{insec}', + checkedout = timestamp '$outdate $form->{outhour}:$form->{outmin}:$form->{outsec}', + employee_id = $form->{employee_id}, + notes = |.$dbh->quote($form->{notes}).qq| + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # save printed, queued + $form->save_status($dbh); + + my %audittrail = ( tablename => 'jcitems', + reference => $form->{id}, + formname => $form->{type}, + action => 'saved', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $rc = $dbh->commit; + + $rc; + +} + + +sub price_matrix_query { + my ($dbh, $project_id, $customer_id) = @_; + + my $query = qq|SELECT p.id AS parts_id, 0 AS customer_id, 0 AS pricegroup_id, + 0 AS pricebreak, p.sellprice, NULL AS validfrom, NULL AS validto, + (SELECT substr(curr,1,3) FROM defaults) AS curr, '' AS pricegroup + FROM parts p + WHERE p.id = ? + + UNION + + SELECT p.*, g.pricegroup + FROM partscustomer p + LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id) + WHERE p.parts_id = ? + AND p.customer_id = $customer_id + + UNION + + SELECT p.*, g.pricegroup + FROM partscustomer p + LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id) + JOIN customer c ON (c.pricegroup_id = g.id) + WHERE p.parts_id = ? + AND c.id = $customer_id + + UNION + + SELECT p.*, '' AS pricegroup + FROM partscustomer p + WHERE p.customer_id = 0 + AND p.pricegroup_id = 0 + AND p.parts_id = ? + + ORDER BY customer_id DESC, pricegroup_id DESC, pricebreak + + |; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + $sth; + +} + + +1; + diff --git a/LedgerSMB/Mailer.pm b/LedgerSMB/Mailer.pm new file mode 100755 index 00000000..db44cff0 --- /dev/null +++ b/LedgerSMB/Mailer.pm @@ -0,0 +1,149 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# mailer package +# +#====================================================================== + +package Mailer; + +sub new { + my ($type) = @_; + my $self = {}; + + bless $self, $type; +} + + +sub send { + my ($self, $out) = @_; + + my $boundary = time; + $boundary = "LedgerSMB-$self->{version}-$boundary"; + my $domain = $self->{from}; + $domain =~ s/(.*?\@|>)//g; + my $msgid = "$boundary\@$domain"; + + $self->{charset} = "ISO-8859-1" unless $self->{charset}; + + if ($out) { + open(OUT, $out) or return "$out : $!"; + } else { + open(OUT, ">-") or return "STDOUT : $!"; + } + + $self->{contenttype} = "text/plain" unless $self->{contenttype}; + + my %h; + for (qw(from to cc bcc)) { + $self->{$_} =~ s/\</</g; + $self->{$_} =~ s/\>/>/g; + $self->{$_} =~ s/(\/|\\|\$)//g; + $h{$_} = $self->{$_}; + } + + $h{cc} = "Cc: $h{cc}\n" if $self->{cc}; + $h{bcc} = "Bcc: $h{bcc}\n" if $self->{bcc}; + $h{notify} = "Disposition-Notification-To: $h{from}\n" if $self->{notify}; + $h{subject} = ($self->{subject} =~ /([\x00-\x1F]|[\x7B-\xFFFF])/) ? "Subject: =?$self->{charset}?B?".&encode_base64($self->{subject},"")."?=" : "Subject: $self->{subject}"; + + print OUT qq|From: $h{from} +To: $h{to} +$h{cc}$h{bcc}$h{subject} +Message-ID: <$msgid> +$h{notify}X-Mailer: LedgerSMB $self->{version} +MIME-Version: 1.0 +|; + + + if (@{ $self->{attachments} }) { + print OUT qq|Content-Type: multipart/mixed; boundary="$boundary" + +|; + if ($self->{message} ne "") { + print OUT qq|--${boundary} +Content-Type: $self->{contenttype}; charset="$self->{charset}" + +$self->{message} + +|; + } + + foreach my $attachment (@{ $self->{attachments} }) { + + my $application = ($attachment =~ /(^\w+$)|\.(html|text|txt|sql)$/) ? "text" : "application"; + + unless (open IN, $attachment) { + close(OUT); + return "$attachment : $!"; + } + + my $filename = $attachment; + # strip path + $filename =~ s/(.*\/|$self->{fileid})//g; + + print OUT qq|--${boundary} +Content-Type: $application/$self->{format}; name="$filename"; charset="$self->{charset}" +Content-Transfer-Encoding: BASE64 +Content-Disposition: attachment; filename="$filename"\n\n|; + + my $msg = ""; + while (<IN>) {; + $msg .= $_; + } + print OUT &encode_base64($msg); + + close(IN); + + } + print OUT qq|--${boundary}--\n|; + + } else { + print OUT qq|Content-Type: $self->{contenttype}; charset="$self->{charset}" + +$self->{message} +|; + } + + close(OUT); + + return ""; + +} + + +sub encode_base64 ($;$) { + + # this code is from the MIME-Base64-2.12 package + # Copyright 1995-1999,2001 Gisle Aas <gisle@ActiveState.com> + + my $res = ""; + my $eol = $_[1]; + $eol = "\n" unless defined $eol; + pos($_[0]) = 0; # ensure start at the beginning + + $res = join '', map( pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs)); + + $res =~ tr|` -_|AA-Za-z0-9+/|; # `# help emacs + # fix padding at the end + my $padding = (3 - length($_[0]) % 3) % 3; + $res =~ s/.{$padding}$/'=' x $padding/e if $padding; + # break encoded string into lines of no more than 60 characters each + if (length $eol) { + $res =~ s/(.{1,60})/$1$eol/g; + } + return $res; + +} + + +1; + diff --git a/LedgerSMB/Menu.pm b/LedgerSMB/Menu.pm new file mode 100755 index 00000000..417f6cba --- /dev/null +++ b/LedgerSMB/Menu.pm @@ -0,0 +1,91 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# routines for menu items +# +#===================================================================== + +package Menu; + +use LedgerSMB::Inifile; +@ISA = qw/Inifile/; + + +sub menuitem { + my ($self, $myconfig, $form, $item) = @_; + + my $module = ($self->{$item}{module}) ? $self->{$item}{module} : $form->{script}; + my $action = ($self->{$item}{action}) ? $self->{$item}{action} : "section_menu"; + my $target = ($self->{$item}{target}) ? $self->{$item}{target} : ""; + + my $level = $form->escape($item); + my $str = qq|<a href="$module?path=$form->{path}&action=$action&level=$level&login=$form->{login}&timeout=$form->{timeout}&sessionid=$form->{sessionid}&js=$form->{js}|; + + my @vars = qw(module action target href); + + if ($self->{$item}{href}) { + $str = qq|<a href="$self->{$item}{href}|; + @vars = qw(module target href); + } + + for (@vars) { delete $self->{$item}{$_} } + + delete $self->{$item}{submenu}; + + # add other params + foreach my $key (keys %{ $self->{$item} }) { + $str .= "&".$form->escape($key)."="; + ($value, $conf) = split /=/, $self->{$item}{$key}, 2; + $value = "$myconfig->{$value}$conf" if $self->{$item}{$key} =~ /=/; + + $str .= $form->escape($value); + } + + $str .= qq|#id$form->{tag}| if $target eq 'acc_menu'; + + if ($target) { + $str .= qq|" target="$target"|; + } + + $str .= qq|>|; + +} + + +sub access_control { + my ($self, $myconfig, $menulevel) = @_; + + my @menu = (); + + if ($menulevel eq "") { + @menu = grep { !/--/ } @{ $self->{ORDER} }; + } else { + @menu = grep { /^${menulevel}--/; } @{ $self->{ORDER} }; + } + + my @a = split /;/, $myconfig->{acs}; + my $excl = (); + + # remove --AR, --AP from array + grep { ($a, $b) = split /--/; s/--$a$//; } @a; + + for (@a) { $excl{$_} = 1 } + + @a = (); + for (@menu) { push @a, $_ unless $excl{$_} } + + @a; + +} + + +1; + diff --git a/LedgerSMB/Num2text.pm b/LedgerSMB/Num2text.pm new file mode 100755 index 00000000..d8ecdef3 --- /dev/null +++ b/LedgerSMB/Num2text.pm @@ -0,0 +1,149 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# this is the default code for the Check package +# +#===================================================================== + + +sub init { + my $self = shift; + + %{ $self->{numbername} } = + (0 => 'Zero', + 1 => 'One', + 2 => 'Two', + 3 => 'Three', + 4 => 'Four', + 5 => 'Five', + 6 => 'Six', + 7 => 'Seven', + 8 => 'Eight', + 9 => 'Nine', + 10 => 'Ten', + 11 => 'Eleven', + 12 => 'Twelve', + 13 => 'Thirteen', + 14 => 'Fourteen', + 15 => 'Fifteen', + 16 => 'Sixteen', + 17 => 'Seventeen', + 18 => 'Eighteen', + 19 => 'Nineteen', + 20 => 'Twenty', + 30 => 'Thirty', + 40 => 'Forty', + 50 => 'Fifty', + 60 => 'Sixty', + 70 => 'Seventy', + 80 => 'Eighty', + 90 => 'Ninety', + 10**2 => 'Hundred', + 10**3 => 'Thousand', + 10**6 => 'Million', + 10**9 => 'Billion', + 10**12 => 'Trillion', + ); + +} + + +sub num2text { + my ($self, $amount) = @_; + + return $self->{numbername}{0} unless $amount; + + my @textnumber = (); + + # split amount into chunks of 3 + my @num = reverse split //, abs($amount); + my @numblock = (); + my @a; + my $i; + + while (@num) { + @a = (); + for (1 .. 3) { + push @a, shift @num; + } + push @numblock, join / /, reverse @a; + } + + while (@numblock) { + + $i = $#numblock; + @num = split //, $numblock[$i]; + + if ($numblock[$i] == 0) { + pop @numblock; + next; + } + + if ($numblock[$i] > 99) { + # the one from hundreds + push @textnumber, $self->{numbername}{$num[0]}; + + # add hundred designation + push @textnumber, $self->{numbername}{10**2}; + + # reduce numblock + $numblock[$i] -= $num[0] * 100; + + } + + $numblock[$i] *= 1; + + if ($numblock[$i] > 9) { + # tens + push @textnumber, $self->format_ten($numblock[$i]); + } elsif ($numblock[$i] > 0) { + # ones + push @textnumber, $self->{numbername}{$numblock[$i]}; + } + + # add thousand, million + if ($i) { + $num = 10**($i * 3); + push @textnumber, $self->{numbername}{$num}; + } + + pop @numblock; + + } + + join ' ', @textnumber; + +} + + +sub format_ten { + my ($self, $amount) = @_; + + my $textnumber = ""; + my @num = split //, $amount; + + if ($amount > 20) { + $textnumber = $self->{numbername}{$num[0]*10}; + $amount = $num[1]; + } else { + $textnumber = $self->{numbername}{$amount}; + $amount = 0; + } + + $textnumber .= " ".$self->{numbername}{$amount} if $amount; + + $textnumber; + +} + + +1; + diff --git a/LedgerSMB/OE.pm b/LedgerSMB/OE.pm new file mode 100755 index 00000000..25bcecd3 --- /dev/null +++ b/LedgerSMB/OE.pm @@ -0,0 +1,2238 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Order entry module +# Quotation +# +#====================================================================== + +package OE; + + +sub transactions { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $null; + my $var; + my $ordnumber = 'ordnumber'; + my $quotation = '0'; + my $department; + + my $rate = ($form->{vc} eq 'customer') ? 'buy' : 'sell'; + + ($form->{transdatefrom}, $form->{transdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{type} =~ /_quotation$/) { + $quotation = '1'; + $ordnumber = 'quonumber'; + } + + my $number = $form->like(lc $form->{$ordnumber}); + my $name = $form->like(lc $form->{$form->{vc}}); + + for (qw(department employee)) { + if ($form->{$_}) { + ($null, $var) = split /--/, $form->{$_}; + $department .= " AND o.${_}_id = $var"; + } + } + + my $query = qq|SELECT o.id, o.ordnumber, o.transdate, o.reqdate, + o.amount, ct.name, o.netamount, o.$form->{vc}_id, + ex.$rate AS exchangerate, + o.closed, o.quonumber, o.shippingpoint, o.shipvia, + e.name AS employee, m.name AS manager, o.curr, o.ponumber + FROM oe o + JOIN $form->{vc} ct ON (o.$form->{vc}_id = ct.id) + LEFT JOIN employee e ON (o.employee_id = e.id) + LEFT JOIN employee m ON (e.managerid = m.id) + LEFT JOIN exchangerate ex ON (ex.curr = o.curr + AND ex.transdate = o.transdate) + WHERE o.quotation = '$quotation' + $department|; + + my %ordinal = ( id => 1, + ordnumber => 2, + transdate => 3, + reqdate => 4, + name => 6, + quonumber => 11, + shipvia => 13, + employee => 14, + manager => 15, + curr => 16, + ponumber => 17 + ); + + my @a = (transdate, $ordnumber, name); + push @a, "employee" if $form->{l_employee}; + if ($form->{type} !~ /(ship|receive)_order/) { + push @a, "manager" if $form->{l_manager}; + } + my $sortorder = $form->sort_order(\@a, \%ordinal); + + + # build query if type eq (ship|receive)_order + if ($form->{type} =~ /(ship|receive)_order/) { + + my ($warehouse, $warehouse_id) = split /--/, $form->{warehouse}; + + $query = qq|SELECT DISTINCT o.id, o.ordnumber, o.transdate, + o.reqdate, o.amount, ct.name, o.netamount, o.$form->{vc}_id, + ex.$rate AS exchangerate, + o.closed, o.quonumber, o.shippingpoint, o.shipvia, + e.name AS employee, o.curr, o.ponumber + FROM oe o + JOIN $form->{vc} ct ON (o.$form->{vc}_id = ct.id) + JOIN orderitems oi ON (oi.trans_id = o.id) + JOIN parts p ON (p.id = oi.parts_id)|; + + if ($warehouse_id && $form->{type} eq 'ship_order') { + $query .= qq| + JOIN inventory i ON (oi.parts_id = i.parts_id) + |; + } + + $query .= qq| + LEFT JOIN employee e ON (o.employee_id = e.id) + LEFT JOIN exchangerate ex ON (ex.curr = o.curr + AND ex.transdate = o.transdate) + WHERE o.quotation = '0' + AND (p.inventory_accno_id > 0 OR p.assembly = '1') + AND oi.qty != oi.ship + $department|; + + if ($warehouse_id && $form->{type} eq 'ship_order') { + $query .= qq| + AND i.warehouse_id = $warehouse_id + AND ( SELECT SUM(i.qty) + FROM inventory i + WHERE oi.parts_id = i.parts_id + AND i.warehouse_id = $warehouse_id ) > 0 + |; + } + + } + + if ($form->{"$form->{vc}_id"}) { + $query .= qq| AND o.$form->{vc}_id = $form->{"$form->{vc}_id"}|; + } else { + if ($form->{$form->{vc}} ne "") { + $query .= " AND lower(ct.name) LIKE '$name'"; + } + } + + if ($form->{$ordnumber} ne "") { + $query .= " AND lower($ordnumber) LIKE '$number'"; + $form->{open} = 1; + $form->{closed} = 1; + } + if ($form->{ponumber} ne "") { + $query .= " AND lower(ponumber) LIKE '$form->{ponumber}'"; + } + + if (!$form->{open} && !$form->{closed}) { + $query .= " AND o.id = 0"; + } elsif (!($form->{open} && $form->{closed})) { + $query .= ($form->{open}) ? " AND o.closed = '0'" : " AND o.closed = '1'"; + } + + if ($form->{shipvia} ne "") { + $var = $form->like(lc $form->{shipvia}); + $query .= " AND lower(o.shipvia) LIKE '$var'"; + } + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $query .= " AND o.id IN (SELECT DISTINCT trans_id + FROM orderitems + WHERE lower(description) LIKE '$var')"; + } + + if ($form->{transdatefrom}) { + $query .= " AND o.transdate >= '$form->{transdatefrom}'"; + } + if ($form->{transdateto}) { + $query .= " AND o.transdate <= '$form->{transdateto}'"; + } + + $query .= " ORDER by $sortorder"; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my %oid = (); + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{exchangerate} = 1 unless $ref->{exchangerate}; + if ($ref->{id} != $oid{id}{$ref->{id}}) { + push @{ $form->{OE} }, $ref; + $oid{vc}{$ref->{curr}}{$ref->{"$form->{vc}_id"}}++; + } + $oid{id}{$ref->{id}} = $ref->{id}; + } + $sth->finish; + + $dbh->disconnect; + + if ($form->{type} =~ /^consolidate_/) { + @a = (); + foreach $ref (@{ $form->{OE} }) { push @a, $ref if $oid{vc}{$ref->{curr}}{$ref->{"$form->{vc}_id"}} > 1 } + + @{ $form->{OE} } = @a; + } + +} + + + +sub save { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn off autocommit + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + my $null; + my $exchangerate = 0; + + ($null, $form->{employee_id}) = split /--/, $form->{employee}; + if (! $form->{employee_id}) { + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + $form->{employee} = "$form->{employee}--$form->{employee_id}"; + } + + my $ml = ($form->{type} eq 'sales_order') ? 1 : -1; + + $query = qq|SELECT p.assembly, p.project_id + FROM parts p + WHERE p.id = ?|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + + if ($form->{id}) { + $query = qq|SELECT id FROM oe + WHERE id = $form->{id}|; + + if ($dbh->selectrow_array($query)) { + &adj_onhand($dbh, $form, $ml) if $form->{type} =~ /_order$/; + + $query = qq|DELETE FROM orderitems + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM shipto + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + } else { + $query = qq|INSERT INTO oe (id) + VALUES ($form->{id})|; + $dbh->do($query) || $form->dberror($query); + } + } + + if (! $form->{id}) { + + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO oe (ordnumber, employee_id) + VALUES ('$uid', $form->{employee_id})|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM oe + WHERE ordnumber = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + ($form->{id}) = $sth->fetchrow_array; + $sth->finish; + + } + + my $amount; + my $linetotal; + my $discount; + my $project_id; + my $taxrate; + my $taxamount; + my $fxsellprice; + my %taxbase; + my @taxaccounts; + my %taxaccounts; + my $netamount = 0; + + + for my $i (1 .. $form->{rowcount}) { + + for (qw(qty ship)) { $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } + + $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100; + $form->{"sellprice_$i"} = $form->parse_amount($myconfig, $form->{"sellprice_$i"}); + + if ($form->{"qty_$i"}) { + + $pth->execute($form->{"id_$i"}); + $ref = $pth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{"${_}_$i"} = $ref->{$_} } + $pth->finish; + + $fxsellprice = $form->{"sellprice_$i"}; + + my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + $discount = $form->round_amount($form->{"sellprice_$i"} * $form->{"discount_$i"}, $decimalplaces); + $form->{"sellprice_$i"} = $form->round_amount($form->{"sellprice_$i"} - $discount, $decimalplaces); + + $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2); + + @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + $taxrate = 0; + $taxdiff = 0; + + for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} } + + if ($form->{taxincluded}) { + $taxamount = $linetotal * $taxrate / (1 + $taxrate); + $taxbase = $linetotal - $taxamount; + # we are not keeping a natural price, do not round + $form->{"sellprice_$i"} = $form->{"sellprice_$i"} * (1 / (1 + $taxrate)); + } else { + $taxamount = $linetotal * $taxrate; + $taxbase = $linetotal; + } + + if (@taxaccounts && $form->round_amount($taxamount, 2) == 0) { + if ($form->{taxincluded}) { + foreach $item (@taxaccounts) { + $taxamount = $form->round_amount($linetotal * $form->{"${item}_rate"} / (1 + abs($form->{"${item}_rate"})), 2); + + $taxaccounts{$item} += $taxamount; + $taxdiff += $taxamount; + + $taxbase{$item} += $taxbase; + } + $taxaccounts{$taxaccounts[0]} += $taxdiff; + } else { + foreach $item (@taxaccounts) { + $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"}; + $taxbase{$item} += $taxbase; + } + } + } else { + foreach $item (@taxaccounts) { + $taxaccounts{$item} += $taxamount * $form->{"${item}_rate"} / $taxrate; + $taxbase{$item} += $taxbase; + } + } + + + $netamount += $form->{"sellprice_$i"} * $form->{"qty_$i"}; + + $project_id = 'NULL'; + if ($form->{"projectnumber_$i"} ne "") { + ($null, $project_id) = split /--/, $form->{"projectnumber_$i"}; + } + $project_id = $form->{"project_id_$i"} if $form->{"project_id_$i"}; + + # save detail record in orderitems table + $query = qq|INSERT INTO orderitems (|; + $query .= "id, " if $form->{"orderitems_id_$i"}; + $query .= qq|trans_id, parts_id, description, qty, sellprice, discount, + unit, reqdate, project_id, ship, serialnumber, notes) + VALUES (|; + $query .= qq|$form->{"orderitems_id_$i"},| if $form->{"orderitems_id_$i"}; + $query .= qq|$form->{id}, $form->{"id_$i"}, | + .$dbh->quote($form->{"description_$i"}).qq|, + $form->{"qty_$i"}, $fxsellprice, $form->{"discount_$i"}, | + .$dbh->quote($form->{"unit_$i"}).qq|, | + .$form->dbquote($form->{"reqdate_$i"}, SQL_DATE).qq|, + $project_id, $form->{"ship_$i"}, | + .$dbh->quote($form->{"serialnumber_$i"}).qq|, | + .$dbh->quote($form->{"notes_$i"}).qq|)|; + $dbh->do($query) || $form->dberror($query); + + $form->{"sellprice_$i"} = $fxsellprice; + } + $form->{"discount_$i"} *= 100; + } + + + # set values which could be empty + for (qw(vendor_id customer_id taxincluded closed quotation)) { $form->{$_} *= 1 } + + # add up the tax + my $tax = 0; + for (keys %taxaccounts) { $tax += $taxaccounts{$_} } + + $amount = $form->round_amount($netamount + $tax, 2); + $netamount = $form->round_amount($netamount, 2); + + if ($form->{currency} eq $form->{defaultcurrency}) { + $form->{exchangerate} = 1; + } else { + $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? 'buy' : 'sell'); + } + + $form->{exchangerate} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{exchangerate}); + + my $quotation; + my $ordnumber; + my $numberfld; + if ($form->{type} =~ /_order$/) { + $quotation = "0"; + $ordnumber = "ordnumber"; + $numberfld = ($form->{vc} eq 'customer') ? "sonumber" : "ponumber"; + } else { + $quotation = "1"; + $ordnumber = "quonumber"; + $numberfld = ($form->{vc} eq 'customer') ? "sqnumber" : "rfqnumber"; + } + + $form->{$ordnumber} = $form->update_defaults($myconfig, $numberfld, $dbh) unless $form->{$ordnumber}; + + ($null, $form->{department_id}) = split(/--/, $form->{department}); + for (qw(department_id terms)) { $form->{$_} *= 1 } + + # save OE record + $query = qq|UPDATE oe set + ordnumber = |.$dbh->quote($form->{ordnumber}).qq|, + quonumber = |.$dbh->quote($form->{quonumber}).qq|, + transdate = '$form->{transdate}', + vendor_id = $form->{vendor_id}, + customer_id = $form->{customer_id}, + amount = $amount, + netamount = $netamount, + reqdate = |.$form->dbquote($form->{reqdate}, SQL_DATE).qq|, + taxincluded = '$form->{taxincluded}', + shippingpoint = |.$dbh->quote($form->{shippingpoint}).qq|, + shipvia = |.$dbh->quote($form->{shipvia}).qq|, + notes = |.$dbh->quote($form->{notes}).qq|, + intnotes = |.$dbh->quote($form->{intnotes}).qq|, + curr = '$form->{currency}', + closed = '$form->{closed}', + quotation = '$quotation', + department_id = $form->{department_id}, + employee_id = $form->{employee_id}, + language_code = '$form->{language_code}', + ponumber = |.$dbh->quote($form->{ponumber}).qq|, + terms = $form->{terms} + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $form->{ordtotal} = $amount; + + # add shipto + $form->{name} = $form->{$form->{vc}}; + $form->{name} =~ s/--$form->{"$form->{vc}_id"}//; + $form->add_shipto($dbh, $form->{id}); + + # save printed, emailed, queued + $form->save_status($dbh); + + if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) { + if ($form->{vc} eq 'customer') { + $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0); + } + if ($form->{vc} eq 'vendor') { + $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate}); + } + } + + + if ($form->{type} =~ /_order$/) { + # adjust onhand + &adj_onhand($dbh, $form, $ml * -1); + &adj_inventory($dbh, $myconfig, $form); + } + + my %audittrail = ( tablename => 'oe', + reference => ($form->{type} =~ /_order$/) ? $form->{ordnumber} : $form->{quonumber}, + formname => $form->{type}, + action => 'saved', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + $form->save_recurring($dbh, $myconfig); + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + + +sub delete { + my ($self, $myconfig, $form, $spool) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + # delete spool files + my $query = qq|SELECT spoolfile FROM status + WHERE trans_id = $form->{id} + AND spoolfile IS NOT NULL|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $spoolfile; + my @spoolfiles = (); + + while (($spoolfile) = $sth->fetchrow_array) { + push @spoolfiles, $spoolfile; + } + $sth->finish; + + + $query = qq|SELECT o.parts_id, o.ship, p.inventory_accno_id, p.assembly + FROM orderitems o + JOIN parts p ON (p.id = o.parts_id) + WHERE trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + if ($form->{type} =~ /_order$/) { + $ml = ($form->{type} eq 'purchase_order') ? -1 : 1; + while (my ($id, $ship, $inv, $assembly) = $sth->fetchrow_array) { + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $id|, + $ship * $ml) if ($inv || $assembly); + } + } + $sth->finish; + + # delete inventory + $query = qq|DELETE FROM inventory + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete status entries + $query = qq|DELETE FROM status + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete OE record + $query = qq|DELETE FROM oe + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete individual entries + $query = qq|DELETE FROM orderitems + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM shipto + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my %audittrail = ( tablename => 'oe', + reference => ($form->{type} =~ /_order$/) ? $form->{ordnumber} : $form->{quonumber}, + formname => $form->{type}, + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $rc = $dbh->commit; + $dbh->disconnect; + + if ($rc) { + foreach $spoolfile (@spoolfiles) { + unlink "$spool/$spoolfile" if $spoolfile; + } + } + + $rc; + +} + + + +sub retrieve { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $sth; + my $var; + my $ref; + + $query = qq|SELECT curr, current_date + FROM defaults|; + ($form->{currencies}, $form->{transdate}) = $dbh->selectrow_array($query); + + if ($form->{id}) { + + # retrieve order + $query = qq|SELECT o.ordnumber, o.transdate, o.reqdate, o.terms, + o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes, + o.curr AS currency, e.name AS employee, o.employee_id, + o.$form->{vc}_id, vc.name AS $form->{vc}, o.amount AS invtotal, + o.closed, o.reqdate, o.quonumber, o.department_id, + d.description AS department, o.language_code, o.ponumber + FROM oe o + JOIN $form->{vc} vc ON (o.$form->{vc}_id = vc.id) + LEFT JOIN employee e ON (o.employee_id = e.id) + LEFT JOIN department d ON (o.department_id = d.id) + WHERE o.id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + $query = qq|SELECT * FROM shipto + WHERE trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{$_} = $ref->{$_} } + $sth->finish; + + # get printed, emailed and queued + $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname + FROM status s + WHERE s.trans_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{printed} .= "$ref->{formname} " if $ref->{printed}; + $form->{emailed} .= "$ref->{formname} " if $ref->{emailed}; + $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile}; + } + $sth->finish; + for (qw(printed emailed queued)) { $form->{$_} =~ s/ +$//g } + + my %oid = ( 'Pg' => 'oid', + 'PgPP' => 'oid', + 'Oracle' => 'rowid', + 'DB2' => '1=1' + ); + + # retrieve individual items + $query = qq|SELECT o.id AS orderitems_id, + p.partnumber, p.assembly, o.description, o.qty, + o.sellprice, o.parts_id AS id, o.unit, o.discount, p.bin, + o.reqdate, o.project_id, o.ship, o.serialnumber, o.notes, + pr.projectnumber, + pg.partsgroup, p.partsgroup_id, p.partnumber AS sku, + p.listprice, p.lastcost, p.weight, p.onhand, + p.inventory_accno_id, p.income_accno_id, p.expense_accno_id, + t.description AS partsgrouptranslation + FROM orderitems o + JOIN parts p ON (o.parts_id = p.id) + LEFT JOIN project pr ON (o.project_id = pr.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + LEFT JOIN translation t ON (t.trans_id = p.partsgroup_id AND t.language_code = '$form->{language_code}') + WHERE o.trans_id = $form->{id} + ORDER BY o.$oid{$myconfig->{dbdriver}}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # foreign exchange rates + &exchangerate_defaults($dbh, $form); + + # query for price matrix + my $pmh = &price_matrix_query($dbh, $form); + + # taxes + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (pt.chart_id = c.id) + WHERE pt.parts_id = ?|; + my $tth = $dbh->prepare($query) || $form->dberror($query); + + my $taxrate; + my $ptref; + my $sellprice; + my $listprice; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + ($decimalplaces) = ($ref->{sellprice} =~ /\.(\d+)/); + $decimalplaces = length $decimalplaces; + $decimalplaces = ($decimalplaces > 2) ? $decimalplaces : 2; + + $tth->execute($ref->{id}); + $ref->{taxaccounts} = ""; + $taxrate = 0; + + while ($ptref = $tth->fetchrow_hashref(NAME_lc)) { + $ref->{taxaccounts} .= "$ptref->{accno} "; + $taxrate += $form->{"$ptref->{accno}_rate"}; + } + $tth->finish; + chop $ref->{taxaccounts}; + + # preserve price + $sellprice = $ref->{sellprice}; + + # multiply by exchangerate + $ref->{sellprice} = $form->round_amount($ref->{sellprice} * $form->{$form->{currency}}, $decimalplaces); + + for (qw(listprice lastcost)) { $ref->{$_} = $form->round_amount($ref->{$_} / $form->{$form->{currency}}, $decimalplaces) } + + # partnumber and price matrix + &price_matrix($pmh, $ref, $form->{transdate}, $decimalplaces, $form, $myconfig); + + $ref->{sellprice} = $sellprice; + + $ref->{partsgroup} = $ref->{partsgrouptranslation} if $ref->{partsgrouptranslation}; + + push @{ $form->{form_details} }, $ref; + + } + $sth->finish; + + # get recurring transaction + $form->get_recurring($dbh); + + } else { + + # get last name used + $form->lastname_used($myconfig, $dbh, $form->{vc}) unless $form->{"$form->{vc}_id"}; + delete $form->{notes}; + + } + + $dbh->disconnect; + +} + + +sub price_matrix_query { + my ($dbh, $form) = @_; + + my $query; + my $sth; + + if ($form->{customer_id}) { + $query = qq|SELECT p.id AS parts_id, 0 AS customer_id, 0 AS pricegroup_id, + 0 AS pricebreak, p.sellprice, NULL AS validfrom, NULL AS validto, + '$form->{defaultcurrency}' AS curr, '' AS pricegroup + FROM parts p + WHERE p.id = ? + + UNION + + SELECT p.*, g.pricegroup + FROM partscustomer p + LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id) + WHERE p.parts_id = ? + AND p.customer_id = $form->{customer_id} + + UNION + + SELECT p.*, g.pricegroup + FROM partscustomer p + LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id) + JOIN customer c ON (c.pricegroup_id = g.id) + WHERE p.parts_id = ? + AND c.id = $form->{customer_id} + + UNION + + SELECT p.*, '' AS pricegroup + FROM partscustomer p + WHERE p.customer_id = 0 + AND p.pricegroup_id = 0 + AND p.parts_id = ? + + ORDER BY customer_id DESC, pricegroup_id DESC, pricebreak + |; + $sth = $dbh->prepare($query) || $form->dberror($query); + } + + if ($form->{vendor_id}) { + # price matrix and vendor's partnumber + $query = qq|SELECT partnumber + FROM partsvendor + WHERE parts_id = ? + AND vendor_id = $form->{vendor_id}|; + $sth = $dbh->prepare($query) || $form->dberror($query); + } + + $sth; + +} + + +sub price_matrix { + my ($pmh, $ref, $transdate, $decimalplaces, $form, $myconfig) = @_; + + $ref->{pricematrix} = ""; + my $customerprice; + my $pricegroupprice; + my $sellprice; + my $mref; + my %p = (); + + # depends if this is a customer or vendor + if ($form->{customer_id}) { + $pmh->execute($ref->{id}, $ref->{id}, $ref->{id}, $ref->{id}); + + while ($mref = $pmh->fetchrow_hashref(NAME_lc)) { + + # check date + if ($mref->{validfrom}) { + next if $transdate < $form->datetonum($myconfig, $mref->{validfrom}); + } + if ($mref->{validto}) { + next if $transdate > $form->datetonum($myconfig, $mref->{validto}); + } + + # convert price + $sellprice = $form->round_amount($mref->{sellprice} * $form->{$mref->{curr}}, $decimalplaces); + + if ($mref->{customer_id}) { + $ref->{sellprice} = $sellprice if !$mref->{pricebreak}; + $p{$mref->{pricebreak}} = $sellprice; + $customerprice = 1; + } + + if ($mref->{pricegroup_id}) { + if (! $customerprice) { + $ref->{sellprice} = $sellprice if !$mref->{pricebreak}; + $p{$mref->{pricebreak}} = $sellprice; + } + $pricegroupprice = 1; + } + + if (!$customerprice && !$pricegroupprice) { + $p{$mref->{pricebreak}} = $sellprice; + } + + } + $pmh->finish; + + if (%p) { + if ($ref->{sellprice}) { + $p{0} = $ref->{sellprice}; + } + for (sort { $a <=> $b } keys %p) { $ref->{pricematrix} .= "${_}:$p{$_} " } + } else { + if ($init) { + $ref->{sellprice} = $form->round_amount($ref->{sellprice}, $decimalplaces); + } else { + $ref->{sellprice} = $form->round_amount($ref->{sellprice} * (1 - $form->{tradediscount}), $decimalplaces); + } + $ref->{pricematrix} = "0:$ref->{sellprice} " if $ref->{sellprice}; + } + chop $ref->{pricematrix}; + + } + + + if ($form->{vendor_id}) { + $pmh->execute($ref->{id}); + + $mref = $pmh->fetchrow_hashref(NAME_lc); + + if ($mref->{partnumber} ne "") { + $ref->{partnumber} = $mref->{partnumber}; + } + + if ($mref->{lastcost}) { + # do a conversion + $ref->{sellprice} = $form->round_amount($mref->{lastcost} * $form->{$mref->{curr}}, $decimalplaces); + } + $pmh->finish; + + $ref->{sellprice} *= 1; + + # add 0:price to matrix + $ref->{pricematrix} = "0:$ref->{sellprice}"; + + } + +} + + +sub exchangerate_defaults { + my ($dbh, $form) = @_; + + my $var; + my $buysell = ($form->{vc} eq "customer") ? "buy" : "sell"; + + # get default currencies + my $query = qq|SELECT substr(curr,1,3), curr FROM defaults|; + ($form->{defaultcurrency}, $form->{currencies}) = $dbh->selectrow_array($query); + + $query = qq|SELECT $buysell + FROM exchangerate + WHERE curr = ? + AND transdate = ?|; + my $eth1 = $dbh->prepare($query) || $form->dberror($query); + $query = qq~SELECT max(transdate || ' ' || $buysell || ' ' || curr) + FROM exchangerate + WHERE curr = ?~; + my $eth2 = $dbh->prepare($query) || $form->dberror($query); + + # get exchange rates for transdate or max + foreach $var (split /:/, substr($form->{currencies},4)) { + $eth1->execute($var, $form->{transdate}); + ($form->{$var}) = $eth1->fetchrow_array; + if (! $form->{$var} ) { + $eth2->execute($var); + + ($form->{$var}) = $eth2->fetchrow_array; + ($null, $form->{$var}) = split / /, $form->{$var}; + $form->{$var} = 1 unless $form->{$var}; + $eth2->finish; + } + $eth1->finish; + } + + $form->{$form->{currency}} = $form->{exchangerate} if $form->{exchangerate}; + $form->{$form->{currency}} ||= 1; + $form->{$form->{defaultcurrency}} = 1; + +} + + +sub order_details { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + my $query; + my $sth; + + my $item; + my $i; + my @sortlist = (); + my $projectnumber; + my $projectdescription; + my $projectnumber_id; + my $translation; + my $partsgroup; + + my %oid = ( 'Pg' => 'oid', + 'PgPP' => 'oid', + 'Oracle' => 'rowid', + 'DB2' => '1=1' + ); + + my @taxaccounts; + my %taxaccounts; + my $tax; + my $taxrate; + my $taxamount; + + my %translations; + + $query = qq|SELECT p.description, t.description + FROM project p + LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}') + WHERE id = ?|; + my $prh = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|SELECT inventory_accno_id, income_accno_id, + expense_accno_id, assembly FROM parts + WHERE id = ?|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + my $sortby; + + # sort items by project and partsgroup + for $i (1 .. $form->{rowcount}) { + + if ($form->{"id_$i"}) { + # account numbers + $pth->execute($form->{"id_$i"}); + $ref = $pth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{"${_}_$i"} = $ref->{$_} } + $pth->finish; + + $projectnumber_id = 0; + $projectnumber = ""; + $form->{partsgroup} = ""; + $form->{projectnumber} = ""; + + if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) { + + $inventory_accno_id = ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) ? "1" : ""; + + if ($form->{groupprojectnumber}) { + ($projectnumber, $projectnumber_id) = split /--/, $form->{"projectnumber_$i"}; + } + if ($form->{grouppartsgroup}) { + ($form->{partsgroup}) = split /--/, $form->{"partsgroup_$i"}; + } + + if ($projectnumber_id && $form->{groupprojectnumber}) { + if ($translation{$projectnumber_id}) { + $form->{projectnumber} = $translation{$projectnumber_id}; + } else { + # get project description + $prh->execute($projectnumber_id); + ($projectdescription, $translation) = $prh->fetchrow_array; + $prh->finish; + + $form->{projectnumber} = ($translation) ? "$projectnumber, $translation" : "$projectnumber, $projectdescription"; + + $translation{$projectnumber_id} = $form->{projectnumber}; + } + } + + if ($form->{grouppartsgroup} && $form->{partsgroup}) { + $form->{projectnumber} .= " / " if $projectnumber_id; + $form->{projectnumber} .= $form->{partsgroup}; + } + + $form->format_string(projectnumber); + + } + + $sortby = qq|$projectnumber$form->{partsgroup}|; + if ($form->{sortby} ne 'runningnumber') { + for (qw(partnumber description bin)) { + $sortby .= $form->{"${_}_$i"} if $form->{sortby} eq $_; + } + } + + push @sortlist, [ $i, qq|$projectnumber$form->{partsgroup}$inventory_accno_id|, $form->{projectnumber}, $projectnumber_id, $form->{partsgroup}, $sortby ]; + } + + } + + delete $form->{projectnumber}; + + # sort the whole thing by project and group + @sortlist = sort { $a->[5] cmp $b->[5] } @sortlist; + + + # if there is a warehouse limit picking + if ($form->{warehouse_id} && $form->{formname} =~ /(pick|packing)_list/) { + # run query to check for inventory + $query = qq|SELECT sum(qty) AS qty + FROM inventory + WHERE parts_id = ? + AND warehouse_id = ?|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + for $i (1 .. $form->{rowcount}) { + $sth->execute($form->{"id_$i"}, $form->{warehouse_id}) || $form->dberror; + + ($qty) = $sth->fetchrow_array; + $sth->finish; + + $form->{"qty_$i"} = 0 if $qty == 0; + + if ($form->parse_amount($myconfig, $form->{"ship_$i"}) > $qty) { + $form->{"ship_$i"} = $form->format_amount($myconfig, $qty); + } + } + } + + + my $runningnumber = 1; + my $sameitem = ""; + my $subtotal; + my $k = scalar @sortlist; + my $j = 0; + + foreach $item (@sortlist) { + $i = $item->[0]; + $j++; + + if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) { + if ($item->[1] ne $sameitem) { + $sameitem = $item->[1]; + + $ok = 0; + + if ($form->{groupprojectnumber}) { + $ok = $form->{"projectnumber_$i"}; + } + if ($form->{grouppartsgroup}) { + $ok = $form->{"partsgroup_$i"} unless $ok; + } + + if ($ok) { + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, ""); + push(@{ $form->{service} }, NULL); + } else { + push(@{ $form->{part} }, NULL); + push(@{ $form->{service} }, ""); + } + + push(@{ $form->{description} }, $item->[2]); + for (qw(taxrates runningnumber number sku qty ship unit bin serialnumber requiredate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + } + } + } + + $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}); + $form->{"ship_$i"} = $form->parse_amount($myconfig, $form->{"ship_$i"}); + + if ($form->{"qty_$i"}) { + + $form->{totalqty} += $form->{"qty_$i"}; + $form->{totalship} += $form->{"ship_$i"}; + $form->{totalweight} += ($form->{"weight_$i"} * $form->{"qty_$i"}); + $form->{totalweightship} += ($form->{"weight_$i"} * $form->{"ship_$i"}); + + # add number, description and qty to $form->{number}, .... + push(@{ $form->{runningnumber} }, $runningnumber++); + push(@{ $form->{number} }, qq|$form->{"partnumber_$i"}|); + push(@{ $form->{sku} }, qq|$form->{"sku_$i"}|); + push(@{ $form->{description} }, qq|$form->{"description_$i"}|); + push(@{ $form->{itemnotes} }, $form->{"notes_$i"}); + push(@{ $form->{qty} }, $form->format_amount($myconfig, $form->{"qty_$i"})); + push(@{ $form->{ship} }, $form->format_amount($myconfig, $form->{"ship_$i"})); + push(@{ $form->{unit} }, qq|$form->{"unit_$i"}|); + push(@{ $form->{bin} }, qq|$form->{"bin_$i"}|); + push(@{ $form->{serialnumber} }, qq|$form->{"serialnumber_$i"}|); + push(@{ $form->{requiredate} }, qq|$form->{"reqdate_$i"}|); + push(@{ $form->{projectnumber} }, qq|$form->{"projectnumber_$i"}|); + + push(@{ $form->{sellprice} }, $form->{"sellprice_$i"}); + + push(@{ $form->{listprice} }, $form->{"listprice_$i"}); + + push(@{ $form->{weight} }, $form->format_amount($myconfig, $form->{"weight_$i"} * $form->{"ship_$i"})); + + my $sellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"}); + my ($dec) = ($sellprice =~ /\.(\d+)/); + $dec = length $dec; + my $decimalplaces = ($dec > 2) ? $dec : 2; + + my $discount = $form->round_amount($sellprice * $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100, $decimalplaces); + + # keep a netprice as well, (sellprice - discount) + $form->{"netprice_$i"} = $sellprice - $discount; + + my $linetotal = $form->round_amount($form->{"qty_$i"} * $form->{"netprice_$i"}, 2); + + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, $form->{"sku_$i"}); + push(@{ $form->{service} }, NULL); + $form->{totalparts} += $linetotal; + } else { + push(@{ $form->{service} }, $form->{"sku_$i"}); + push(@{ $form->{part} }, NULL); + $form->{totalservices} += $linetotal; + } + + push(@{ $form->{netprice} }, ($form->{"netprice_$i"}) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : " "); + + $discount = ($discount) ? $form->format_amount($myconfig, $discount * -1, $decimalplaces) : " "; + + push(@{ $form->{discount} }, $discount); + push(@{ $form->{discountrate} }, $form->format_amount($myconfig, $form->{"discount_$i"})); + + $form->{ordtotal} += $linetotal; + + # this is for the subtotals for grouping + $subtotal += $linetotal; + + $form->{"linetotal_$i"} = $form->format_amount($myconfig, $linetotal, 2); + push(@{ $form->{linetotal} }, $form->{"linetotal_$i"}); + + @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + + my $ml = 1; + my @taxrates = (); + + $tax = 0; + + for (0 .. 1) { + $taxrate = 0; + + for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 } + + $taxrate *= $ml; + $taxamount = $linetotal * $taxrate / (1 + $taxrate); + $taxbase = ($linetotal - $taxamount); + + foreach $item (@taxaccounts) { + if (($form->{"${item}_rate"} * $ml) > 0) { + + push @taxrates, $form->{"${item}_rate"} * 100; + + if ($form->{taxincluded}) { + $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"} / (1 + $taxrate); + $taxbase{$item} += $taxbase; + } else { + $taxbase{$item} += $linetotal; + $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"}; + } + } + } + + if ($form->{taxincluded}) { + $tax += $linetotal * ($taxrate / (1 + ($taxrate * $ml))); + } else { + $tax += $linetotal * $taxrate; + } + + $ml *= -1; + } + + push(@{ $form->{lineitems} }, { amount => $linetotal, tax => $form->round_amount($tax, 2) }); + push(@{ $form->{taxrates} }, join ' ', sort { $a <=> $b } @taxrates); + + if ($form->{"assembly_$i"}) { + $form->{stagger} = -1; + &assembly_details($myconfig, $form, $dbh, $form->{"id_$i"}, $oid{$myconfig->{dbdriver}}, $form->{"qty_$i"}); + } + + } + + # add subtotal + if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) { + if ($subtotal) { + if ($j < $k) { + # look at next item + if ($sortlist[$j]->[1] ne $sameitem) { + + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, ""); + push(@{ $form->{service} }, NULL); + } else { + push(@{ $form->{service} }, ""); + push(@{ $form->{part} }, NULL); + } + + for (qw(taxrates runningnumber number sku qty ship unit bin serialnumber requiredate projectnumber sellprice listprice netprice discount discountrate weight itemnotes)) { push(@{ $form->{$_} }, "") } + + push(@{ $form->{description} }, $form->{groupsubtotaldescription}); + + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + + if ($form->{groupsubtotaldescription} ne "") { + push(@{ $form->{linetotal} }, $form->format_amount($myconfig, $subtotal, 2)); + } else { + push(@{ $form->{linetotal} }, ""); + } + $subtotal = 0; + } + + } else { + + # got last item + if ($form->{groupsubtotaldescription} ne "") { + + if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) { + push(@{ $form->{part} }, ""); + push(@{ $form->{service} }, NULL); + } else { + push(@{ $form->{service} }, ""); + push(@{ $form->{part} }, NULL); + } + + for (qw(taxrates runningnumber number sku qty ship unit bin serialnumber requiredate projectnumber sellprice listprice netprice discount discountrate weight itemnotes)) { push(@{ $form->{$_} }, "") } + + push(@{ $form->{description} }, $form->{groupsubtotaldescription}); + push(@{ $form->{linetotal} }, $form->format_amount($myconfig, $subtotal, 2)); + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + } + } + } + } + } + + + $tax = 0; + foreach $item (sort keys %taxaccounts) { + if ($form->round_amount($taxaccounts{$item}, 2)) { + $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2); + + push(@{ $form->{taxbaseinclusive} }, $form->{"${item}_taxbaseinclusive"} = $form->round_amount($taxbase{$item} + $tax, 2)); + push(@{ $form->{taxbase} }, $form->{"${item}_taxbase"} = $form->format_amount($myconfig, $taxbase{$item}, 2)); + push(@{ $form->{tax} }, $form->{"${item}_tax"} = $form->format_amount($myconfig, $taxamount, 2)); + + push(@{ $form->{taxdescription} }, $form->{"${item}_description"}); + + $form->{"${item}_taxrate"} = $form->format_amount($myconfig, $form->{"${item}_rate"} * 100); + push(@{ $form->{taxrate} }, $form->{"${item}_taxrate"}); + + push(@{ $form->{taxnumber} }, $form->{"${item}_taxnumber"}); + } + } + + # adjust taxes for lineitems + my $total = 0; + for (@{ $form->{lineitems} }) { + $total += $_->{tax}; + } + if ($form->round_amount($total,2) != $form->round_amount($tax,2)) { + # get largest amount + for (reverse sort { $a->{tax} <=> $b->{tax} } @{ $form->{lineitems} }) { + $_->{tax} -= $total - $tax; + last; + } + } + $i = 1; + for (@{ $form->{lineitems} }) { + push(@{ $form->{linetax} }, $form->format_amount($myconfig, $_->{tax}, 2, "")); + } + + + for (qw(totalparts totalservices)) { $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) } + for (qw(totalqty totalship totalweight)) { $form->{$_} = $form->format_amount($myconfig, $form->{$_}) } + $form->{subtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2); + $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax; + + use LedgerSMB::CP; + my $c; + if ($form->{language_code} ne "") { + $c = new CP $form->{language_code}; + } else { + $c = new CP $myconfig->{countrycode}; + } + $c->init; + my $whole; + ($whole, $form->{decimal}) = split /\./, $form->{ordtotal}; + $form->{decimal} .= "00"; + $form->{decimal} = substr($form->{decimal}, 0, 2); + + $form->{text_decimal} = $c->num2text($form->{decimal} * 1); + $form->{text_amount} = $c->num2text($whole); + $form->{integer_amount} = $form->format_amount($myconfig, $whole); + + # format amounts + $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2); + + $form->format_string(qw(text_amount text_decimal)); + + $query = qq|SELECT weightunit FROM defaults|; + ($form->{weightunit}) = $dbh->selectrow_array($query); + + $dbh->disconnect; + +} + + +sub assembly_details { + my ($myconfig, $form, $dbh, $id, $oid, $qty) = @_; + + my $sm = ""; + my $spacer; + + $form->{stagger}++; + if ($form->{format} eq 'html') { + $spacer = " " x (3 * ($form->{stagger} - 1)) if $form->{stagger} > 1; + } + if ($form->{format} =~ /(postscript|pdf)/) { + if ($form->{stagger} > 1) { + $spacer = ($form->{stagger} - 1) * 3; + $spacer = '\rule{'.$spacer.'mm}{0mm}'; + } + } + + # get parts and push them onto the stack + my $sortorder = ""; + + if ($form->{grouppartsgroup}) { + $sortorder = qq|ORDER BY pg.partsgroup, a.$oid|; + } else { + $sortorder = qq|ORDER BY a.$oid|; + } + + my $where = ($form->{formname} eq 'work_order') ? "1 = 1" : "a.bom = '1'"; + + my $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, + pg.partsgroup, p.partnumber AS sku, p.assembly, p.id, p.bin + FROM assembly a + JOIN parts p ON (a.parts_id = p.id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + WHERE $where + AND a.id = '$id' + $sortorder|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + for (qw(partnumber description partsgroup)) { + $form->{"a_$_"} = $ref->{$_}; + $form->format_string("a_$_"); + } + + if ($form->{grouppartsgroup} && $ref->{partsgroup} ne $sm) { + for (qw(taxrates number sku unit qty runningnumber ship bin serialnumber requiredate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + $sm = ($form->{"a_partsgroup"}) ? $form->{"a_partsgroup"} : ""; + push(@{ $form->{description} }, "$spacer$sm"); + + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + + } + + if ($form->{stagger}) { + + push(@{ $form->{description} }, qq|$spacer$form->{"a_partnumber"}, $form->{"a_description"}|); + for (qw(taxrates number sku runningnumber ship serialnumber requiredate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + + } else { + + push(@{ $form->{description} }, qq|$form->{"a_description"}|); + push(@{ $form->{sku} }, $form->{"a_partnumber"}); + push(@{ $form->{number} }, $form->{"a_partnumber"}); + + for (qw(taxrates runningnumber ship serialnumber requiredate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") } + + } + + push(@{ $form->{lineitems} }, { amount => 0, tax => 0 }); + + push(@{ $form->{qty} }, $form->format_amount($myconfig, $ref->{qty} * $qty)); + for (qw(unit bin)) { + $form->{"a_$_"} = $ref->{$_}; + $form->format_string("a_$_"); + push(@{ $form->{$_} }, $form->{"a_$_"}); + } + + if ($ref->{assembly} && $form->{formname} eq 'work_order') { + &assembly_details($myconfig, $form, $dbh, $ref->{id}, $oid, $ref->{qty} * $qty); + } + + } + $sth->finish; + + $form->{stagger}--; + +} + + +sub project_description { + my ($self, $dbh, $id) = @_; + + my $query = qq|SELECT description + FROM project + WHERE id = $id|; + ($_) = $dbh->selectrow_array($query); + + $_; + +} + + +sub get_warehouses { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + # setup warehouses + my $query = qq|SELECT id, description + FROM warehouse + ORDER BY 2|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_warehouse} }, $ref; + } + $sth->finish; + + $dbh->disconnect; + +} + + +sub save_inventory { + my ($self, $myconfig, $form) = @_; + + my ($null, $warehouse_id) = split /--/, $form->{warehouse}; + $warehouse_id *= 1; + + my $ml = ($form->{type} eq 'ship_order') ? -1 : 1; + + my $dbh = $form->dbconnect_noauto($myconfig); + my $sth; + my $wth; + my $serialnumber; + my $ship; + + my ($null, $employee_id) = split /--/, $form->{employee}; + ($null, $employee_id) = $form->get_employee($dbh) if ! $employee_id; + + $query = qq|SELECT serialnumber, ship + FROM orderitems + WHERE trans_id = ? + AND id = ? + FOR UPDATE|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|SELECT sum(qty) + FROM inventory + WHERE parts_id = ? + AND warehouse_id = ?|; + $wth = $dbh->prepare($query) || $form->dberror($query); + + + for my $i (1 .. $form->{rowcount}) { + + $ship = (abs($form->{"ship_$i"}) > abs($form->{"qty_$i"})) ? $form->{"qty_$i"} : $form->{"ship_$i"}; + + if ($warehouse_id && $form->{type} eq 'ship_order') { + + $wth->execute($form->{"id_$i"}, $warehouse_id) || $form->dberror; + + ($qty) = $wth->fetchrow_array; + $wth->finish; + + if ($ship > $qty) { + $ship = $qty; + } + } + + + if ($ship) { + + $ship *= $ml; + $query = qq|INSERT INTO inventory (parts_id, warehouse_id, + qty, trans_id, orderitems_id, shippingdate, employee_id) + VALUES ($form->{"id_$i"}, $warehouse_id, + $ship, $form->{"id"}, + $form->{"orderitems_id_$i"}, '$form->{shippingdate}', + $employee_id)|; + $dbh->do($query) || $form->dberror($query); + + # add serialnumber, ship to orderitems + $sth->execute($form->{id}, $form->{"orderitems_id_$i"}) || $form->dberror; + ($serialnumber, $ship) = $sth->fetchrow_array; + $sth->finish; + + $serialnumber .= " " if $serialnumber; + $serialnumber .= qq|$form->{"serialnumber_$i"}|; + $ship += $form->{"ship_$i"}; + + $query = qq|UPDATE orderitems SET + serialnumber = '$serialnumber', + ship = $ship, + reqdate = '$form->{shippingdate}' + WHERE trans_id = $form->{id} + AND id = $form->{"orderitems_id_$i"}|; + $dbh->do($query) || $form->dberror($query); + + # update order with ship via + $query = qq|UPDATE oe SET + shippingpoint = |.$dbh->quote($form->{shippingpoint}).qq|, + shipvia = |.$dbh->quote($form->{shipvia}).qq| + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # update onhand for parts + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $form->{"id_$i"}|, + $form->{"ship_$i"} * $ml); + + } + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub adj_onhand { + my ($dbh, $form, $ml) = @_; + + my $query = qq|SELECT oi.parts_id, oi.ship, p.inventory_accno_id, p.assembly + FROM orderitems oi + JOIN parts p ON (p.id = oi.parts_id) + WHERE oi.trans_id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|SELECT sum(p.inventory_accno_id), p.assembly + FROM parts p + JOIN assembly a ON (a.parts_id = p.id) + WHERE a.id = ? + GROUP BY p.assembly|; + my $ath = $dbh->prepare($query) || $form->dberror($query); + + my $ref; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + if ($ref->{inventory_accno_id} || $ref->{assembly}) { + + # do not update if assembly consists of all services + if ($ref->{assembly}) { + $ath->execute($ref->{parts_id}) || $form->dberror($query); + + my ($inv, $assembly) = $ath->fetchrow_array; + $ath->finish; + + next unless ($inv || $assembly); + + } + + # adjust onhand in parts table + $form->update_balance($dbh, + "parts", + "onhand", + qq|id = $ref->{parts_id}|, + $ref->{ship} * $ml); + } + } + + $sth->finish; + +} + + +sub adj_inventory { + my ($dbh, $myconfig, $form) = @_; + + my %oid = ( 'Pg' => 'oid', + 'PgPP' => 'oid', + 'Oracle' => 'rowid', + 'DB2' => '1=1' + ); + + # increase/reduce qty in inventory table + my $query = qq|SELECT oi.id, oi.parts_id, oi.ship + FROM orderitems oi + WHERE oi.trans_id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $query = qq|SELECT $oid{$myconfig->{dbdriver}} AS oid, qty, + (SELECT SUM(qty) FROM inventory + WHERE trans_id = $form->{id} + AND orderitems_id = ?) AS total + FROM inventory + WHERE trans_id = $form->{id} + AND orderitems_id = ?|; + my $ith = $dbh->prepare($query) || $form->dberror($query); + + my $qty; + my $ml = ($form->{type} =~ /(ship|sales)_order/) ? -1 : 1; + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + + $ith->execute($ref->{id}, $ref->{id}) || $form->dberror($query); + + my $ship = $ref->{ship}; + while (my $inv = $ith->fetchrow_hashref(NAME_lc)) { + + if (($qty = (($inv->{total} * $ml) - $ship)) >= 0) { + $qty = $inv->{qty} * $ml if ($qty > ($inv->{qty} * $ml)); + + $form->update_balance($dbh, + "inventory", + "qty", + qq|$oid{$myconfig->{dbdriver}} = $inv->{oid}|, + $qty * -1 * $ml); + $ship -= $qty; + } + } + $ith->finish; + + } + $sth->finish; + + # delete inventory entries if qty = 0 + $query = qq|DELETE FROM inventory + WHERE trans_id = $form->{id} + AND qty = 0|; + $dbh->do($query) || $form->dberror($query); + +} + + +sub get_inventory { + my ($self, $myconfig, $form) = @_; + + my $where; + my $query; + my $null; + my $fromwarehouse_id; + my $towarehouse_id; + my $var; + + my $dbh = $form->dbconnect($myconfig); + + if ($form->{partnumber} ne "") { + $var = $form->like(lc $form->{partnumber}); + $where .= " + AND lower(p.partnumber) LIKE '$var'"; + } + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $where .= " + AND lower(p.description) LIKE '$var'"; + } + if ($form->{partsgroup} ne "") { + ($null, $var) = split /--/, $form->{partsgroup}; + $where .= " + AND pg.id = $var"; + } + + + ($null, $fromwarehouse_id) = split /--/, $form->{fromwarehouse}; + $fromwarehouse_id *= 1; + + ($null, $towarehouse_id) = split /--/, $form->{towarehouse}; + $towarehouse_id *= 1; + + my %ordinal = ( partnumber => 2, + description => 3, + partsgroup => 5, + warehouse => 6, + ); + + my @a = (partnumber, warehouse); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + if ($fromwarehouse_id) { + if ($towarehouse_id) { + $where .= " + AND NOT i.warehouse_id = $towarehouse_id"; + } + $query = qq|SELECT p.id, p.partnumber, p.description, + sum(i.qty) * 2 AS onhand, sum(i.qty) AS qty, + pg.partsgroup, w.description AS warehouse, i.warehouse_id + FROM inventory i + JOIN parts p ON (p.id = i.parts_id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + JOIN warehouse w ON (w.id = i.warehouse_id) + WHERE i.warehouse_id = $fromwarehouse_id + $where + GROUP BY p.id, p.partnumber, p.description, pg.partsgroup, w.description, i.warehouse_id + ORDER BY $sortorder|; + } else { + if ($towarehouse_id) { + $query = qq| + SELECT p.id, p.partnumber, p.description, + p.onhand, (SELECT SUM(qty) FROM inventory i WHERE i.parts_id = p.id) AS qty, + pg.partsgroup, '' AS warehouse, 0 AS warehouse_id + FROM parts p + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + WHERE p.onhand > 0 + $where + UNION|; + } + + $query .= qq| + SELECT p.id, p.partnumber, p.description, + sum(i.qty) * 2 AS onhand, sum(i.qty) AS qty, + pg.partsgroup, w.description AS warehouse, i.warehouse_id + FROM inventory i + JOIN parts p ON (p.id = i.parts_id) + LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) + JOIN warehouse w ON (w.id = i.warehouse_id) + WHERE i.warehouse_id != $towarehouse_id + $where + GROUP BY p.id, p.partnumber, p.description, pg.partsgroup, w.description, i.warehouse_id + ORDER BY $sortorder|; + } + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{qty} = $ref->{onhand} - $ref->{qty}; + push @{ $form->{all_inventory} }, $ref if $ref->{qty} > 0; + } + $sth->finish; + + $dbh->disconnect; + +} + + +sub transfer { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect_noauto($myconfig); + + ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh); + + my @a = localtime; + $a[5] += 1900; + $a[4]++; + $a[4] = substr("0$a[4]", -2); + $a[3] = substr("0$a[3]", -2); + $shippingdate = "$a[5]$a[4]$a[3]"; + + my %total = (); + + my $query = qq|INSERT INTO inventory + (warehouse_id, parts_id, qty, shippingdate, employee_id) + VALUES (?, ?, ?, '$shippingdate', $form->{employee_id})|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + my $qty; + + for my $i (1 .. $form->{rowcount}) { + $qty = $form->parse_amount($myconfig, $form->{"transfer_$i"}); + + $qty = $form->{"qty_$i"} if ($qty > $form->{"qty_$i"}); + + if ($qty > 0) { + # to warehouse + if ($form->{warehouse_id}) { + $sth->execute($form->{warehouse_id}, $form->{"id_$i"}, $qty) || $form->dberror; + $sth->finish; + } + + # from warehouse + if ($form->{"warehouse_id_$i"}) { + $sth->execute($form->{"warehouse_id_$i"}, $form->{"id_$i"}, $qty * -1) || $form->dberror; + $sth->finish; + } + } + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub get_soparts { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $id; + my $ref; + + # store required items from selected sales orders + my $query = qq|SELECT p.id, oi.qty - oi.ship AS required, + p.assembly + FROM orderitems oi + JOIN parts p ON (p.id = oi.parts_id) + WHERE oi.trans_id = ?|; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + for (my $i = 1; $i <= $form->{rowcount}; $i++) { + + if ($form->{"ndx_$i"}) { + + $sth->execute($form->{"ndx_$i"}); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + &add_items_required("", $dbh, $form, $ref->{id}, $ref->{required}, $ref->{assembly}); + } + $sth->finish; + } + + } + + $query = qq|SELECT current_date FROM defaults|; + ($form->{transdate}) = $dbh->selectrow_array($query); + + # foreign exchange rates + &exchangerate_defaults($dbh, $form); + + $dbh->disconnect; + +} + + +sub add_items_required { + my ($self, $dbh, $form, $parts_id, $required, $assembly) = @_; + + my $query; + my $sth; + my $ref; + + if ($assembly) { + $query = qq|SELECT p.id, a.qty, p.assembly + FROM assembly a + JOIN parts p ON (p.id = a.parts_id) + WHERE a.id = $parts_id|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + &add_items_required("", $dbh, $form, $ref->{id}, $required * $ref->{qty}, $ref->{assembly}); + } + $sth->finish; + + } else { + + $query = qq|SELECT partnumber, description, lastcost + FROM parts + WHERE id = $parts_id|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + $ref = $sth->fetchrow_hashref(NAME_lc); + for (keys %$ref) { $form->{orderitems}{$parts_id}{$_} = $ref->{$_} } + $sth->finish; + + $form->{orderitems}{$parts_id}{required} += $required; + + $query = qq|SELECT pv.partnumber, pv.leadtime, pv.lastcost, pv.curr, + pv.vendor_id, v.name + FROM partsvendor pv + JOIN vendor v ON (v.id = pv.vendor_id) + WHERE pv.parts_id = ?|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + # get cost and vendor + $sth->execute($parts_id); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + for (keys %$ref) { $form->{orderitems}{$parts_id}{partsvendor}{$ref->{vendor_id}}{$_} = $ref->{$_} } + } + $sth->finish; + + } + +} + + +sub generate_orders { + my ($self, $myconfig, $form) = @_; + + my $i; + my %a; + my $query; + my $sth; + + for ($i = 1; $i <= $form->{rowcount}; $i++) { + for (qw(qty lastcost)) { $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } + + if ($form->{"qty_$i"}) { + ($vendor, $vendor_id) = split /--/, $form->{"vendor_$i"}; + if ($vendor_id) { + $a{$vendor_id}{$form->{"id_$i"}}{qty} += $form->{"qty_$i"}; + for (qw(curr lastcost)) { $a{$vendor_id}{$form->{"id_$i"}}{$_} = $form->{"${_}_$i"} } + } + } + } + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + # foreign exchange rates + &exchangerate_defaults($dbh, $form); + + my $amount; + my $netamount; + my $curr = ""; + my %tax; + my $taxincluded = 0; + my $vendor_id; + + my $description; + my $unit; + + my $sellprice; + + foreach $vendor_id (keys %a) { + + %tax = (); + + $query = qq|SELECT v.curr, v.taxincluded, t.rate, c.accno + FROM vendor v + LEFT JOIN vendortax vt ON (v.id = vt.vendor_id) + LEFT JOIN tax t ON (t.chart_id = vt.chart_id) + LEFT JOIN chart c ON (c.id = t.chart_id) + WHERE v.id = $vendor_id|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $curr = $ref->{curr}; + $taxincluded = $ref->{taxincluded}; + $tax{$ref->{accno}} = $ref->{rate}; + } + $sth->finish; + + $curr ||= $form->{defaultcurrency}; + $taxincluded *= 1; + + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO oe (ordnumber) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM oe + WHERE ordnumber = '$uid'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + my ($id) = $sth->fetchrow_array; + $sth->finish; + + $amount = 0; + $netamount = 0; + + foreach my $parts_id (keys %{ $a{$vendor_id} }) { + + if (($form->{$curr} * $form->{$a{$vendor_id}{$parts_id}{curr}}) > 0) { + $sellprice = $a{$vendor_id}{$parts_id}{lastcost} / $form->{$curr} * $form->{$a{$vendor_id}{$parts_id}{curr}}; + } else { + $sellprice = $a{$vendor_id}{$parts_id}{lastcost}; + } + $sellprice = $form->round_amount($sellprice, 2); + + my $linetotal = $form->round_amount($sellprice * $a{$vendor_id}{$parts_id}{qty}, 2); + + $query = qq|SELECT p.description, p.unit, c.accno FROM parts p + LEFT JOIN partstax pt ON (p.id = pt.parts_id) + LEFT JOIN chart c ON (c.id = pt.chart_id) + WHERE p.id = $parts_id|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $rate = 0; + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $description = $ref->{description}; + $unit = $ref->{unit}; + $rate += $tax{$ref->{accno}}; + } + $sth->finish; + + $netamount += $linetotal; + if ($taxincluded) { + $amount += $linetotal; + } else { + $amount += $form->round_amount($linetotal * (1 + $rate), 2); + } + + $description = $dbh->quote($description); + $unit = $dbh->quote($unit); + + $query = qq|INSERT INTO orderitems (trans_id, parts_id, description, + qty, ship, sellprice, unit) VALUES + ($id, $parts_id, $description, + $a{$vendor_id}{$parts_id}{qty}, 0, $sellprice, $unit)|; + $dbh->do($query) || $form->dberror($query); + + } + + my $ordnumber = $form->update_defaults($myconfig, 'ponumber'); + + my $null; + my $employee_id; + my $department_id; + + ($null, $employee_id) = $form->get_employee($dbh); + ($null, $department_id) = split /--/, $form->{department}; + $department_id *= 1; + + $query = qq|UPDATE oe SET + ordnumber = |.$dbh->quote($ordnumber).qq|, + transdate = current_date, + vendor_id = $vendor_id, + customer_id = 0, + amount = $amount, + netamount = $netamount, + taxincluded = '$taxincluded', + curr = '$curr', + employee_id = $employee_id, + department_id = '$department_id', + ponumber = |.$dbh->quote($form->{ponumber}).qq| + WHERE id = $id|; + $dbh->do($query) || $form->dberror($query); + + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub consolidate_orders { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $i; + my $id; + my $ref; + my %oe = (); + + my $query = qq|SELECT * FROM oe + WHERE id = ?|; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + for ($i = 1; $i <= $form->{rowcount}; $i++) { + # retrieve order + if ($form->{"ndx_$i"}) { + $sth->execute($form->{"ndx_$i"}); + + $ref = $sth->fetchrow_hashref(NAME_lc); + $ref->{ndx} = $i; + $oe{oe}{$ref->{curr}}{$ref->{id}} = $ref; + + $oe{vc}{$ref->{curr}}{$ref->{"$form->{vc}_id"}}++; + $sth->finish; + } + } + + $query = qq|SELECT * FROM orderitems + WHERE trans_id = ?|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + foreach $curr (keys %{ $oe{oe} }) { + + foreach $id (sort { $oe{oe}{$curr}{$a}->{ndx} <=> $oe{oe}{$curr}{$b}->{ndx} } keys %{ $oe{oe}{$curr} }) { + + # retrieve order + $vc_id = $oe{oe}{$curr}{$id}->{"$form->{vc}_id"}; + + if ($oe{vc}{$oe{oe}{$curr}{$id}->{curr}}{$vc_id} > 1) { + + push @{ $oe{orders}{$curr}{$vc_id} }, $id; + + $sth->execute($id); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $oe{orderitems}{$curr}{$id} }, $ref; + } + $sth->finish; + + } + } + } + + + my $ordnumber = $form->{ordnumber}; + my $numberfld = ($form->{vc} eq 'customer') ? 'sonumber' : 'ponumber'; + + my ($department, $department_id) = $form->{department}; + $department_id *= 1; + + my $uid = localtime; + $uid .= "$$"; + + my @orderitems = (); + + foreach $curr (keys %{ $oe{orders} }) { + + foreach $vc_id (sort { $a <=> $b } keys %{ $oe{orders}{$curr} }) { + # the orders + @orderitems = (); + $form->{customer_id} = $form->{vendor_id} = 0; + $form->{"$form->{vc}_id"} = $vc_id; + $amount = 0; + $netamount = 0; + + foreach $id (@{ $oe{orders}{$curr}{$vc_id} }) { + + # header + $ref = $oe{oe}{$curr}{$id}; + + $amount += $ref->{amount}; + $netamount += $ref->{netamount}; + + foreach $item (@{ $oe{orderitems}{$curr}{$id} }) { + push @orderitems, $item; + } + + # close order + $query = qq|UPDATE oe SET + closed = '1' + WHERE id = $id|; + $dbh->do($query) || $form->dberror($query); + + # reset shipped + $query = qq|UPDATE orderitems SET + ship = 0 + WHERE trans_id = $id|; + $dbh->do($query) || $form->dberror($query); + + } + + $ordnumber ||= $form->update_defaults($myconfig, $numberfld, $dbh); + + $query = qq|INSERT INTO oe (ordnumber) + VALUES ($uid)|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM oe + WHERE ordnumber = '$uid'|; + ($id) = $dbh->selectrow_array($query); + + $ref->{employee_id} *= 1; + + $query = qq|UPDATE oe SET + ordnumber = |.$dbh->quote($ordnumber).qq|, + transdate = current_date, + vendor_id = $form->{vendor_id}, + customer_id = $form->{customer_id}, + amount = $amount, + netamount = $netamount, + reqdate = |.$form->dbquote($ref->{reqdate}, SQL_DATE).qq|, + taxincluded = '$ref->{taxincluded}', + shippingpoint = |.$dbh->quote($ref->{shippingpoint}).qq|, + notes = |.$dbh->quote($ref->{notes}).qq|, + curr = '$curr', + employee_id = $ref->{employee_id}, + intnotes = |.$dbh->quote($ref->{intnotes}).qq|, + shipvia = |.$dbh->quote($ref->{shipvia}).qq|, + language_code = '$ref->{language_code}', + ponumber = |.$dbh->quote($form->{ponumber}).qq|, + department_id = $department_id + WHERE id = $id|; + $dbh->do($query) || $form->dberror($query); + + + # add items + foreach $item (@orderitems) { + for (qw(qty sellprice discount project_id ship)) { $item->{$_} *= 1 } + $query = qq|INSERT INTO orderitems ( + trans_id, parts_id, description, + qty, sellprice, discount, + unit, reqdate, project_id, + ship, serialnumber, notes) + VALUES ( + $id, $item->{parts_id}, |.$dbh->quote($item->{description}).qq|, + $item->{qty}, $item->{sellprice}, $item->{discount}, + |.$dbh->quote($item->{unit}).qq|, |.$form->dbquote($item->{reqdate}, SQL_DATE).qq|, $item->{project_id}, + $item->{ship}, |.$dbh->quote($item->{serialnumber}).qq|, |.$dbh->quote($item->{notes}).qq|)|; + + $dbh->do($query) || $form->dberror($query); + + } + } + } + + + $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +1; + diff --git a/LedgerSMB/OP.pm b/LedgerSMB/OP.pm new file mode 100755 index 00000000..65b8da31 --- /dev/null +++ b/LedgerSMB/OP.pm @@ -0,0 +1,101 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Overpayment function +# used in AR, AP, IS, IR, OE, CP +# +#====================================================================== + +package OP; + +sub overpayment { + my ($self, $myconfig, $form, $dbh, $amount, $ml) = @_; + + my $fxamount = $form->round_amount($amount * $form->{exchangerate}, 2); + my ($paymentaccno) = split /--/, $form->{account}; + + my ($null, $department_id) = split /--/, $form->{department}; + $department_id *= 1; + + my $uid = localtime; + $uid .= "$$"; + + # add AR/AP header transaction with a payment + $query = qq|INSERT INTO $form->{arap} (invnumber, employee_id) + VALUES ('$uid', (SELECT id FROM employee + WHERE login = '$form->{login}'))|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM $form->{arap} + WHERE invnumber = '$uid'|; + ($uid) = $dbh->selectrow_array($query); + + my $invnumber = $form->{invnumber}; + $invnumber = $form->update_defaults($myconfig, ($form->{arap} eq 'ar') ? "sinumber" : "vinumber", $dbh) unless $invnumber; + + $query = qq|UPDATE $form->{arap} set + invnumber = |.$dbh->quote($invnumber).qq|, + $form->{vc}_id = $form->{"$form->{vc}_id"}, + transdate = '$form->{datepaid}', + datepaid = '$form->{datepaid}', + duedate = '$form->{datepaid}', + netamount = 0, + amount = 0, + paid = $fxamount, + curr = '$form->{currency}', + department_id = $department_id + WHERE id = $uid|; + $dbh->do($query) || $form->dberror($query); + + # add AR/AP + ($accno) = split /--/, $form->{$form->{ARAP}}; + + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, amount) + VALUES ($uid, (SELECT id FROM chart + WHERE accno = '$accno'), + '$form->{datepaid}', $fxamount * $ml)|; + $dbh->do($query) || $form->dberror($query); + + # add payment + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, source, memo) + VALUES ($uid, (SELECT id FROM chart + WHERE accno = '$paymentaccno'), + '$form->{datepaid}', $amount * $ml * -1, | + .$dbh->quote($form->{source}).qq|, | + .$dbh->quote($form->{memo}).qq|)|; + $dbh->do($query) || $form->dberror($query); + + # add exchangerate difference + if ($fxamount != $amount) { + $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate, + amount, cleared, fx_transaction, source) + VALUES ($uid, (SELECT id FROM chart + WHERE accno = '$paymentaccno'), + '$form->{datepaid}', ($fxamount - $amount) * $ml * -1, + '1', '1', | + .$dbh->quote($form->{source}).qq|)|; + $dbh->do($query) || $form->dberror($query); + } + + my %audittrail = ( tablename => $form->{arap}, + reference => $invnumber, + formname => ($form->{arap} eq 'ar') ? 'deposit' : 'pre-payment', + action => 'posted', + id => $uid ); + + $form->audittrail($dbh, "", \%audittrail); + +} + + +1; + diff --git a/LedgerSMB/PE.pm b/LedgerSMB/PE.pm new file mode 100755 index 00000000..d85f4cc3 --- /dev/null +++ b/LedgerSMB/PE.pm @@ -0,0 +1,1499 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Project module +# also used for partsgroups +# +#====================================================================== + +package PE; + + +sub projects { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{sort} = "projectnumber" unless $form->{sort}; + my @a = ($form->{sort}); + my %ordinal = ( projectnumber => 2, + description => 3, + startdate => 4, + enddate => 5, + ); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query; + my $where = "WHERE 1=1"; + + $query = qq|SELECT pr.*, c.name + FROM project pr + LEFT JOIN customer c ON (c.id = pr.customer_id)|; + + if ($form->{type} eq 'job') { + $where .= qq| AND pr.id NOT IN (SELECT DISTINCT id + FROM parts + WHERE project_id > 0)|; + } + + my $var; + if ($form->{projectnumber} ne "") { + $var = $form->like(lc $form->{projectnumber}); + $where .= " AND lower(pr.projectnumber) LIKE '$var'"; + } + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $where .= " AND lower(pr.description) LIKE '$var'"; + } + + ($form->{startdatefrom}, $form->{startdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{startdatefrom}) { + $where .= " AND (pr.startdate IS NULL OR pr.startdate >= '$form->{startdatefrom}')"; + } + if ($form->{startdateto}) { + $where .= " AND (pr.startdate IS NULL OR pr.startdate <= '$form->{startdateto}')"; + } + + if ($form->{status} eq 'orphaned') { + $where .= qq| AND pr.id NOT IN (SELECT DISTINCT project_id + FROM acc_trans + WHERE project_id > 0 + UNION + SELECT DISTINCT project_id + FROM invoice + WHERE project_id > 0 + UNION + SELECT DISTINCT project_id + FROM orderitems + WHERE project_id > 0 + UNION + SELECT DISTINCT project_id + FROM jcitems + WHERE project_id > 0) + |; + + } + if ($form->{status} eq 'active') { + $where .= qq| AND (pr.enddate IS NULL OR pr.enddate >= current_date)|; + } + if ($form->{status} eq 'inactive') { + $where .= qq| AND pr.enddate <= current_date|; + } + + $query .= qq| + $where + ORDER BY $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $i = 0; + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_project} }, $ref; + $i++; + } + + $sth->finish; + $dbh->disconnect; + + $i; + +} + + +sub get_project { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $sth; + my $ref; + my $where; + + if ($form->{id}) { + + $where = "WHERE pr.id = $form->{id}" if $form->{id}; + + $query = qq|SELECT pr.*, + c.name AS customer + FROM project pr + LEFT JOIN customer c ON (c.id = pr.customer_id) + $where|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + + # check if it is orphaned + $query = qq|SELECT count(*) + FROM acc_trans + WHERE project_id = $form->{id} + UNION + SELECT count(*) + FROM invoice + WHERE project_id = $form->{id} + UNION + SELECT count(*) + FROM orderitems + WHERE project_id = $form->{id} + UNION + SELECT count(*) + FROM jcitems + WHERE project_id = $form->{id} + |; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $count; + while (($count) = $sth->fetchrow_array) { + $form->{orphaned} += $count; + } + $sth->finish; + $form->{orphaned} = !$form->{orphaned}; + } + + PE->get_customer($myconfig, $form, $dbh); + + $dbh->disconnect; + +} + + +sub save_project { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{customer_id} ||= 'NULL'; + + $form->{projectnumber} = $form->update_defaults($myconfig, "projectnumber", $dbh) unless $form->{projectnumber}; + + if ($form->{id}) { + + $query = qq|UPDATE project SET + projectnumber = |.$dbh->quote($form->{projectnumber}).qq|, + description = |.$dbh->quote($form->{description}).qq|, + startdate = |.$form->dbquote($form->{startdate}, SQL_DATE).qq|, + enddate = |.$form->dbquote($form->{enddate}, SQL_DATE).qq|, + customer_id = $form->{customer_id} + WHERE id = $form->{id}|; + } else { + + $query = qq|INSERT INTO project + (projectnumber, description, startdate, enddate, customer_id) + VALUES (| + .$dbh->quote($form->{projectnumber}).qq|, | + .$dbh->quote($form->{description}).qq|, | + .$form->dbquote($form->{startdate}, SQL_DATE).qq|, | + .$form->dbquote($form->{enddate}, SQL_DATE).qq|, + $form->{customer_id} + )|; + } + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + +sub list_stock { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $var; + my $where = "1 = 1"; + + if ($form->{status} eq 'active') { + $where = qq|(pr.enddate IS NULL + OR pr.enddate >= current_date) + AND pr.completed < pr.production|; + } + if ($form->{status} eq 'inactive') { + $where = qq|pr.completed = pr.production|; + } + + if ($form->{projectnumber}) { + $var = $form->like(lc $form->{projectnumber}); + $where .= " AND lower(pr.projectnumber) LIKE '$var'"; + } + + if ($form->{description}) { + $var = $form->like(lc $form->{description}); + $where .= " AND lower(pr.description) LIKE '$var'"; + } + + $form->{sort} = "projectnumber" unless $form->{sort}; + my @a = ($form->{sort}); + my %ordinal = ( projectnumber => 2, + description => 3 + ); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT pr.*, p.partnumber + FROM project pr + JOIN parts p ON (p.id = pr.parts_id) + WHERE $where + ORDER BY $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_project} }, $ref; + } + $sth->finish; + + $query = qq|SELECT current_date FROM defaults|; + ($form->{stockingdate}) = $dbh->selectrow_array($query) if !$form->{stockingdate}; + + $dbh->disconnect; + +} + + +sub jobs { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{sort} = "projectnumber" unless $form->{sort}; + my @a = ($form->{sort}); + my %ordinal = ( projectnumber => 2, + description => 3, + startdate => 4, + ); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT pr.*, p.partnumber, p.onhand, c.name + FROM project pr + JOIN parts p ON (p.id = pr.parts_id) + LEFT JOIN customer c ON (c.id = pr.customer_id) + WHERE 1=1|; + + if ($form->{projectnumber} ne "") { + $var = $form->like(lc $form->{projectnumber}); + $query .= " AND lower(pr.projectnumber) LIKE '$var'"; + } + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $query .= " AND lower(pr.description) LIKE '$var'"; + } + + ($form->{startdatefrom}, $form->{startdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{startdatefrom}) { + $query .= " AND pr.startdate >= '$form->{startdatefrom}'"; + } + if ($form->{startdateto}) { + $query .= " AND pr.startdate <= '$form->{startdateto}'"; + } + + if ($form->{status} eq 'active') { + $query .= qq| AND NOT pr.production = pr.completed|; + } + if ($form->{status} eq 'inactive') { + $query .= qq| AND pr.production = pr.completed|; + } + if ($form->{status} eq 'orphaned') { + $query .= qq| AND pr.completed = 0 + AND (pr.id NOT IN SELECT DISTINCT project_id + FROM invoice + WHERE project_id > 0) + UNION + SELECT DISTINCT project_id + FROM orderitems + WHERE project_id > 0 + SELECT DISTINCT project_id + FROM jcitems + WHERE project_id > 0 + )|; + } + + $query .= qq| + ORDER BY $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_project} }, $ref; + } + + $sth->finish; + + $dbh->disconnect; + +} + + +sub get_job { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query; + my $sth; + my $ref; + + if ($form->{id}) { + $query = qq|SELECT weightunit + FROM defaults|; + ($form->{weightunit}) = $dbh->selectrow_array($query); + + $query = qq|SELECT pr.*, + p.partnumber, p.description AS partdescription, p.unit, p.listprice, + p.sellprice, p.priceupdate, p.weight, p.notes, p.bin, + p.partsgroup_id, + ch.accno AS income_accno, ch.description AS income_description, + pr.customer_id, c.name AS customer, + pg.partsgroup + FROM project pr + LEFT JOIN parts p ON (p.id = pr.parts_id) + LEFT JOIN chart ch ON (ch.id = p.income_accno_id) + LEFT JOIN customer c ON (c.id = pr.customer_id) + LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + WHERE pr.id = $form->{id}|; + } else { + $query = qq|SELECT weightunit, current_date AS startdate FROM defaults|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + $ref = $sth->fetchrow_hashref(NAME_lc); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + + if ($form->{id}) { + # check if it is orphaned + $query = qq|SELECT count(*) + FROM invoice + WHERE project_id = $form->{id} + UNION + SELECT count(*) + FROM orderitems + WHERE project_id = $form->{id} + UNION + SELECT count(*) + FROM jcitems + WHERE project_id = $form->{id} + |; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $count; + while (($count) = $sth->fetchrow_array) { + $form->{orphaned} += $count; + } + $sth->finish; + + } + + $form->{orphaned} = !$form->{orphaned}; + + $query = qq|SELECT accno, description, link + FROM chart + WHERE link LIKE '%IC%' + ORDER BY accno|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + for (split /:/, $ref->{link}) { + if (/IC/) { + push @{ $form->{IC_links}{$_} }, { accno => $ref->{accno}, + description => $ref->{description} }; + } + } + } + $sth->finish; + + if ($form->{id}) { + $query = qq|SELECT ch.accno + FROM parts p + JOIN partstax pt ON (pt.parts_id = p.id) + JOIN chart ch ON (pt.chart_id = ch.id) + WHERE p.id = $form->{id}|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{amount}{$ref->{accno}} = $ref->{accno}; + } + $sth->finish; + } + + PE->get_customer($myconfig, $form, $dbh); + + $dbh->disconnect; + +} + + +sub get_customer { + my ($self, $myconfig, $form, $dbh) = @_; + + my $disconnect = 0; + + if (! $dbh) { + $dbh = $form->dbconnect($myconfig); + $disconnect = 1; + } + + my $query; + my $sth; + my $ref; + + if (! $form->{startdate}) { + $query = qq|SELECT current_date FROM defaults|; + ($form->{startdate}) = $dbh->selectrow_array($query); + } + + my $where = qq|(startdate >= '$form->{startdate}' OR startdate IS NULL OR enddate IS NULL)|; + + if ($form->{enddate}) { + $where .= qq| AND (enddate >= '$form->{enddate}' OR enddate IS NULL)|; + } else { + $where .= qq| AND (enddate >= current_date OR enddate IS NULL)|; + } + + $query = qq|SELECT count(*) + FROM customer + WHERE $where|; + my ($count) = $dbh->selectrow_array($query); + + if ($count < $myconfig->{vclimit}) { + $query = qq|SELECT id, name + FROM customer + WHERE $where|; + + if ($form->{customer_id}) { + $query .= qq| + UNION SELECT id,name + FROM customer + WHERE id = $form->{customer_id}|; + } + + $query .= qq| + ORDER BY name|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + @{ $form->{all_customer} } = (); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_customer} }, $ref; + } + $sth->finish; + } + + $dbh->disconnect if $disconnect; + +} + + +sub save_job { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my ($income_accno) = split /--/, $form->{IC_income}; + + my ($partsgroup, $partsgroup_id) = split /--/, $form->{partsgroup}; + $partsgroup_id ||= 'NULL'; + + if ($form->{id}) { + $query = qq|SELECT id FROM project + WHERE id = $form->{id}|; + ($form->{id}) = $dbh->selectrow_array($query); + } + + if (!$form->{id}) { + my $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO project (projectnumber) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id FROM project + WHERE projectnumber = '$uid'|; + ($form->{id}) = $dbh->selectrow_array($query); + } + + $form->{projectnumber} = $form->update_defaults($myconfig, "projectnumber", $dbh) unless $form->{projectnumber}; + + $query = qq|UPDATE project SET + projectnumber = |.$dbh->quote($form->{projectnumber}).qq|, + description = |.$dbh->quote($form->{description}).qq|, + startdate = |.$form->dbquote($form->{startdate}, SQL_DATE).qq|, + enddate = |.$form->dbquote($form->{enddate}, SQL_DATE).qq|, + parts_id = $form->{id}, + production = |.$form->parse_amount($myconfig, $form->{production}).qq|, + customer_id = $form->{customer_id} + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + + #### add/edit assembly + $query = qq|SELECT id FROM parts + WHERE id = $form->{id}|; + my ($id) = $dbh->selectrow_array($query); + + if (!$id) { + $query = qq|INSERT INTO parts (id) + VALUES ($form->{id})|; + $dbh->do($query) || $form->dberror($query); + } + + my $partnumber = ($form->{partnumber}) ? $form->{partnumber} : $form->{projectnumber}; + + $query = qq|UPDATE parts SET + partnumber = |.$dbh->quote($partnumber).qq|, + description = |.$dbh->quote($form->{partdescription}).qq|, + priceupdate = |.$form->dbquote($form->{priceupdate}, SQL_DATE).qq|, + listprice = |.$form->parse_amount($myconfig, $form->{listprice}).qq|, + sellprice = |.$form->parse_amount($myconfig, $form->{sellprice}).qq|, + weight = |.$form->parse_amount($myconfig, $form->{weight}).qq|, + bin = '$form->{bin}', + unit = |.$dbh->quote($form->{unit}).qq|, + notes = |.$dbh->quote($form->{notes}).qq|, + income_accno_id = (SELECT id FROM chart + WHERE accno = '$income_accno'), + partsgroup_id = $partsgroup_id, + assembly = '1', + obsolete = '1', + project_id = $form->{id} + WHERE id = $form->{id}|; + + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM partstax + WHERE parts_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + for (split / /, $form->{taxaccounts}) { + if ($form->{"IC_tax_$_"}) { + $query = qq|INSERT INTO partstax (parts_id, chart_id) + VALUES ($form->{id}, + (SELECT id + FROM chart + WHERE accno = '$_'))|; + $dbh->do($query) || $form->dberror($query); + } + } + + $dbh->commit; + $dbh->disconnect; + +} + + +sub stock_assembly { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $ref; + + my $query = qq|SELECT * + FROM project + WHERE id = ?|; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|SELECT COUNT(*) + FROM parts + WHERE project_id = ?|; + my $rvh = $dbh->prepare($query) || $form->dberror($query); + + if (! $form->{stockingdate}) { + $query = qq|SELECT current_date FROM defaults|; + ($form->{stockingdate}) = $dbh->selectrow_array($query); + } + + $query = qq|SELECT * + FROM parts + WHERE id = ?|; + my $pth = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|SELECT j.*, p.lastcost FROM jcitems j + JOIN parts p ON (p.id = j.parts_id) + WHERE j.project_id = ? + AND j.checkedin <= '$form->{stockingdate}' + ORDER BY parts_id|; + my $jth = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|INSERT INTO assembly (id, parts_id, qty, bom, adj) + VALUES (?, ?, ?, '0', '0')|; + my $ath = $dbh->prepare($query) || $form->dberror($query); + + my $i = 0; + my $sold; + my $ship; + + while (1) { + $i++; + last unless $form->{"id_$i"}; + + $stock = $form->parse_amount($myconfig, $form->{"stock_$i"}); + + if ($stock) { + $sth->execute($form->{"id_$i"}); + $ref = $sth->fetchrow_hashref(NAME_lc); + + if ($stock > ($ref->{production} - $ref->{completed})) { + $stock = $ref->{production} - $ref->{completed}; + } + if (($stock * -1) > $ref->{completed}) { + $stock = $ref->{completed} * -1; + } + + $pth->execute($form->{"id_$i"}); + $pref = $pth->fetchrow_hashref(NAME_lc); + + my %assembly = (); + my $lastcost = 0; + my $sellprice = 0; + my $listprice = 0; + + $jth->execute($form->{"id_$i"}); + while ($jref = $jth->fetchrow_hashref(NAME_lc)) { + $assembly{qty}{$jref->{parts_id}} += ($jref->{qty} - $jref->{allocated}); + $assembly{parts_id}{$jref->{parts_id}} = $jref->{parts_id}; + $assembly{jcitems}{$jref->{id}} = $jref->{id}; + $lastcost += $form->round_amount(($jref->{lastcost} * ($jref->{qty} - $jref->{allocated})), 2); + $sellprice += $form->round_amount(($jref->{sellprice} * ($jref->{qty} - $jref->{allocated})), 2); + $listprice += $form->round_amount(($jref->{listprice} * ($jref->{qty} - $jref->{allocated})), 2); + } + $jth->finish; + + $uid = localtime; + $uid .= "$$"; + + $query = qq|INSERT INTO parts (partnumber) + VALUES ('$uid')|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|SELECT id + FROM parts + WHERE partnumber = '$uid'|; + ($uid) = $dbh->selectrow_array($query); + + $lastcost = $form->round_amount($lastcost / $stock, 2); + $sellprice = ($pref->{sellprice}) ? $pref->{sellprice} : $form->round_amount($sellprice / $stock, 2); + $listprice = ($pref->{listprice}) ? $pref->{listprice} : $form->round_amount($listprice / $stock, 2); + + $rvh->execute($form->{"id_$i"}); + my ($rev) = $rvh->fetchrow_array; + $rvh->finish; + + $query = qq|UPDATE parts SET + partnumber = '$pref->{partnumber}-$rev', + description = '$pref->{partdescription}', + priceupdate = '$form->{stockingdate}', + unit = '$pref->{unit}', + listprice = $listprice, + sellprice = $sellprice, + lastcost = $lastcost, + weight = $pref->{weight}, + onhand = $stock, + notes = '$pref->{notes}', + assembly = '1', + income_accno_id = $pref->{income_accno_id}, + bin = '$pref->{bin}', + project_id = $form->{"id_$i"} + WHERE id = $uid|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO partstax (parts_id, chart_id) + SELECT '$uid', chart_id FROM partstax + WHERE parts_id = $pref->{id}|; + $dbh->do($query) || $form->dberror($query); + + + $pth->finish; + + for (keys %{$assembly{parts_id}}) { + if ($assembly{qty}{$_}) { + $ath->execute($uid, $assembly{parts_id}{$_}, $form->round_amount($assembly{qty}{$_} / $stock, 4)); + $ath->finish; + } + } + + $form->update_balance($dbh, + "project", + "completed", + qq|id = $form->{"id_$i"}|, + $stock); + + $query = qq|UPDATE jcitems SET + allocated = qty + WHERE allocated != qty + AND checkedin <= '$form->{stockingdate}' + AND project_id = $form->{"id_$i"}|; + $dbh->do($query) || $form->dberror($query); + + $sth->finish; + + } + + } + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub delete_project { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + $query = qq|DELETE FROM project + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM translation + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub delete_partsgroup { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + $query = qq|DELETE FROM partsgroup + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM translation + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub delete_pricegroup { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + $query = qq|DELETE FROM pricegroup + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub delete_job { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my %audittrail = ( tablename => 'project', + reference => $form->{id}, + formname => $form->{type}, + action => 'deleted', + id => $form->{id} ); + + $form->audittrail($dbh, "", \%audittrail); + + my $query = qq|DELETE FROM project + WHERE id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM translation + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + # delete all the assemblies + $query = qq|DELETE FROM assembly a + JOIN parts p ON (a.id = p.id) + WHERE p.project_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|DELETE FROM parts + WHERE project_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + my $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +sub partsgroups { + my ($self, $myconfig, $form) = @_; + + my $var; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{sort} = "partsgroup" unless $form->{partsgroup}; + my @a = (partsgroup); + my $sortorder = $form->sort_order(\@a); + + my $query = qq|SELECT g.* + FROM partsgroup g|; + + my $where = "1 = 1"; + + if ($form->{partsgroup} ne "") { + $var = $form->like(lc $form->{partsgroup}); + $where .= " AND lower(partsgroup) LIKE '$var'"; + } + $query .= qq| + WHERE $where + ORDER BY $sortorder|; + + if ($form->{status} eq 'orphaned') { + $query = qq|SELECT g.* + FROM partsgroup g + LEFT JOIN parts p ON (p.partsgroup_id = g.id) + WHERE $where + EXCEPT + SELECT g.* + FROM partsgroup g + JOIN parts p ON (p.partsgroup_id = g.id) + WHERE $where + ORDER BY $sortorder|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $i = 0; + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{item_list} }, $ref; + $i++; + } + + $sth->finish; + $dbh->disconnect; + + $i; + +} + + +sub save_partsgroup { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + if ($form->{id}) { + $query = qq|UPDATE partsgroup SET + partsgroup = |.$dbh->quote($form->{partsgroup}).qq| + WHERE id = $form->{id}|; + } else { + $query = qq|INSERT INTO partsgroup + (partsgroup) + VALUES (|.$dbh->quote($form->{partsgroup}).qq|)|; + } + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + +sub get_partsgroup { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT * + FROM partsgroup + WHERE id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + + # check if it is orphaned + $query = qq|SELECT count(*) + FROM parts + WHERE partsgroup_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{orphaned}) = $sth->fetchrow_array; + $form->{orphaned} = !$form->{orphaned}; + + $sth->finish; + + $dbh->disconnect; + +} + + +sub pricegroups { + my ($self, $myconfig, $form) = @_; + + my $var; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + $form->{sort} = "pricegroup" unless $form->{sort}; + my @a = (pricegroup); + my $sortorder = $form->sort_order(\@a); + + my $query = qq|SELECT g.* + FROM pricegroup g|; + + my $where = "1 = 1"; + + if ($form->{pricegroup} ne "") { + $var = $form->like(lc $form->{pricegroup}); + $where .= " AND lower(pricegroup) LIKE '$var'"; + } + $query .= qq| + WHERE $where + ORDER BY $sortorder|; + + if ($form->{status} eq 'orphaned') { + $query = qq|SELECT g.* + FROM pricegroup g + WHERE $where + AND g.id NOT IN (SELECT DISTINCT pricegroup_id + FROM partscustomer + WHERE pricegroup_id > 0) + ORDER BY $sortorder|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $i = 0; + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{item_list} }, $ref; + $i++; + } + + $sth->finish; + $dbh->disconnect; + + $i; + +} + + +sub save_pricegroup { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + if ($form->{id}) { + $query = qq|UPDATE pricegroup SET + pricegroup = |.$dbh->quote($form->{pricegroup}).qq| + WHERE id = $form->{id}|; + } else { + $query = qq|INSERT INTO pricegroup + (pricegroup) + VALUES (|.$dbh->quote($form->{pricegroup}).qq|)|; + } + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + +sub get_pricegroup { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT * + FROM pricegroup + WHERE id = $form->{id}|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + + for (keys %$ref) { $form->{$_} = $ref->{$_} } + + $sth->finish; + + # check if it is orphaned + $query = qq|SELECT count(*) + FROM partscustomer + WHERE pricegroup_id = $form->{id}|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + ($form->{orphaned}) = $sth->fetchrow_array; + $form->{orphaned} = !$form->{orphaned}; + + $sth->finish; + + $dbh->disconnect; + +} + + +sub description_translations { + my ($self, $myconfig, $form) = @_; + + my $where = "1 = 1"; + my $var; + my $ref; + + for (qw(partnumber description)) { + if ($form->{$_}) { + $var = $form->like(lc $form->{$_}); + $where .= " AND lower(p.$_) LIKE '$var'"; + } + } + + $where .= " AND p.obsolete = '0'"; + $where .= " AND p.id = $form->{id}" if $form->{id}; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my %ordinal = ( 'partnumber' => 2, + 'description' => 3 + ); + + my @a = qw(partnumber description); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT l.description AS language, t.description AS translation, + l.code + FROM translation t + JOIN language l ON (l.code = t.language_code) + WHERE trans_id = ? + ORDER BY 1|; + my $tth = $dbh->prepare($query); + + $query = qq|SELECT p.id, p.partnumber, p.description + FROM parts p + WHERE $where + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $tra; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{translations} }, $ref; + + # get translations for description + $tth->execute($ref->{id}) || $form->dberror; + + while ($tra = $tth->fetchrow_hashref(NAME_lc)) { + $form->{trans_id} = $ref->{id}; + $tra->{id} = $ref->{id}; + push @{ $form->{translations} }, $tra; + } + $tth->finish; + + } + $sth->finish; + + &get_language("", $dbh, $form) if $form->{id}; + + $dbh->disconnect; + +} + + +sub partsgroup_translations { + my ($self, $myconfig, $form) = @_; + + my $where = "1 = 1"; + my $ref; + my $var; + + if ($form->{description}) { + $var = $form->like(lc $form->{description}); + $where .= " AND lower(p.partsgroup) LIKE '$var'"; + } + $where .= " AND p.id = $form->{id}" if $form->{id}; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT l.description AS language, t.description AS translation, + l.code + FROM translation t + JOIN language l ON (l.code = t.language_code) + WHERE trans_id = ? + ORDER BY 1|; + my $tth = $dbh->prepare($query); + + $form->sort_order(); + + $query = qq|SELECT p.id, p.partsgroup AS description + FROM partsgroup p + WHERE $where + ORDER BY 2 $form->{direction}|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $tra; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{translations} }, $ref; + + # get translations for partsgroup + $tth->execute($ref->{id}) || $form->dberror; + + while ($tra = $tth->fetchrow_hashref(NAME_lc)) { + $form->{trans_id} = $ref->{id}; + push @{ $form->{translations} }, $tra; + } + $tth->finish; + + } + $sth->finish; + + &get_language("", $dbh, $form) if $form->{id}; + + $dbh->disconnect; + +} + + +sub project_translations { + my ($self, $myconfig, $form) = @_; + + my $where = "1 = 1"; + my $var; + my $ref; + + for (qw(projectnumber description)) { + if ($form->{$_}) { + $var = $form->like(lc $form->{$_}); + $where .= " AND lower(p.$_) LIKE '$var'"; + } + } + + $where .= " AND p.id = $form->{id}" if $form->{id}; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my %ordinal = ( 'projectnumber' => 2, + 'description' => 3 + ); + + my @a = qw(projectnumber description); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $query = qq|SELECT l.description AS language, t.description AS translation, + l.code + FROM translation t + JOIN language l ON (l.code = t.language_code) + WHERE trans_id = ? + ORDER BY 1|; + my $tth = $dbh->prepare($query); + + $query = qq|SELECT p.id, p.projectnumber, p.description + FROM project p + WHERE $where + ORDER BY $sortorder|; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $tra; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{translations} }, $ref; + + # get translations for description + $tth->execute($ref->{id}) || $form->dberror; + + while ($tra = $tth->fetchrow_hashref(NAME_lc)) { + $form->{trans_id} = $ref->{id}; + $tra->{id} = $ref->{id}; + push @{ $form->{translations} }, $tra; + } + $tth->finish; + + } + $sth->finish; + + &get_language("", $dbh, $form) if $form->{id}; + + $dbh->disconnect; + +} + + +sub get_language { + my ($self, $dbh, $form) = @_; + + # get language + my $query = qq|SELECT * + FROM language + ORDER BY 2|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_language} }, $ref; + } + $sth->finish; + +} + + +sub save_translation { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query = qq|DELETE FROM translation + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $query = qq|INSERT INTO translation (trans_id, language_code, description) + VALUES ($form->{id}, ?, ?)|; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + foreach my $i (1 .. $form->{translation_rows}) { + if ($form->{"language_code_$i"} ne "") { + $sth->execute($form->{"language_code_$i"}, $form->{"translation_$i"}); + $sth->finish; + } + } + $dbh->commit; + $dbh->disconnect; + +} + + +sub delete_translation { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|DELETE FROM translation + WHERE trans_id = $form->{id}|; + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + +sub project_sales_order { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT current_date FROM defaults|; + my ($transdate) = $dbh->selectrow_array($query); + + $form->all_years($myconfig, $dbh); + + $form->all_projects($myconfig, $dbh, $transdate); + + $form->all_employees($myconfig, $dbh, $transdate); + + $dbh->disconnect; + +} + + +sub get_jcitems { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $null; + my $var; + my $where; + + if ($form->{projectnumber}) { + ($null, $var) = split /--/, $form->{projectnumber}; + $where .= " AND j.project_id = $var"; + } + + if ($form->{employee}) { + ($null, $var) = split /--/, $form->{employee}; + $where .= " AND j.employee_id = $var"; + } + + ($form->{transdatefrom}, $form->{transdateto}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{transdatefrom}) { + $where .= " AND j.checkedin >= '$form->{transdatefrom}'"; + } + if ($form->{transdateto}) { + $where .= " AND j.checkedout <= (date '$form->{transdateto}' + interval '1 days')"; + } + + my $query; + my $ref; + + $query = qq|SELECT j.id, j.description, j.qty - j.allocated AS qty, + j.sellprice, j.parts_id, pr.$form->{vc}_id, j.project_id, + j.checkedin::date AS transdate, j.notes, + c.name AS $form->{vc}, pr.projectnumber, p.partnumber + FROM jcitems j + JOIN project pr ON (pr.id = j.project_id) + JOIN employee e ON (e.id = j.employee_id) + JOIN parts p ON (p.id = j.parts_id) + LEFT JOIN $form->{vc} c ON (c.id = pr.$form->{vc}_id) + WHERE pr.parts_id IS NULL + AND j.allocated != j.qty + $where + ORDER BY pr.projectnumber, c.name, j.checkedin::date|; + + if ($form->{summary}) { + $query =~ s/j\.description/p\.description/; + $query =~ s/c\.name,/c\.name, j\.parts_id, /; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # tax accounts + $query = qq|SELECT c.accno + FROM chart c + JOIN partstax pt ON (pt.chart_id = c.id) + WHERE pt.parts_id = ?|; + my $tth = $dbh->prepare($query) || $form->dberror($query); + my $ptref; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + $tth->execute($ref->{parts_id}); + $ref->{taxaccounts} = ""; + while ($ptref = $tth->fetchrow_hashref(NAME_lc)) { + $ref->{taxaccounts} .= "$ptref->{accno} "; + } + $tth->finish; + chop $ref->{taxaccounts}; + + $ref->{amount} = $ref->{sellprice} * $ref->{qty}; + + push @{ $form->{jcitems} }, $ref; + } + + $sth->finish; + + $query = qq|SELECT curr + FROM defaults|; + ($form->{currency}) = $dbh->selectrow_array($query); + $form->{currency} =~ s/:.*//; + $form->{defaultcurrency} = $form->{currency}; + + $query = qq|SELECT c.accno, t.rate + FROM tax t + JOIN chart c ON (c.id = t.chart_id)|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{taxaccounts} .= "$ref->{accno} "; + $form->{"$ref->{accno}_rate"} = $ref->{rate}; + } + chop $form->{taxaccounts}; + $sth->finish; + + $dbh->disconnect; + +} + + +sub allocate_projectitems { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect_noauto($myconfig); + + for my $i (1 .. $form->{rowcount}) { + for (split / /, $form->{"jcitems_$i"}) { + my ($id, $qty) = split /:/, $_; + $form->update_balance($dbh, + 'jcitems', + 'allocated', + "id = $id", + $qty); + } + } + + $rc = $dbh->commit; + $dbh->disconnect; + + $rc; + +} + + +1; + diff --git a/LedgerSMB/RC.pm b/LedgerSMB/RC.pm new file mode 100755 index 00000000..8b518cba --- /dev/null +++ b/LedgerSMB/RC.pm @@ -0,0 +1,391 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# Account reconciliation routines +# +#====================================================================== + +package RC; + + +sub paymentaccounts { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT accno, description + FROM chart + WHERE link LIKE '%_paid%' + AND (category = 'A' OR category = 'L') + ORDER BY accno|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{PR} }, $ref; + } + $sth->finish; + + $form->all_years($myconfig, $dbh); + + $dbh->disconnect; + +} + + +sub payment_transactions { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $query; + my $sth; + + $query = qq|SELECT category FROM chart + WHERE accno = '$form->{accno}'|; + ($form->{category}) = $dbh->selectrow_array($query); + + my $cleared; + + ($form->{fromdate}, $form->{todate}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + my $transdate = qq| AND ac.transdate < date '$form->{fromdate}'|; + + if (! $form->{fromdate}) { + $cleared = qq| AND ac.cleared = '1'|; + $transdate = ""; + } + + # get beginning balance + $query = qq|SELECT sum(ac.amount) + FROM acc_trans ac + JOIN chart ch ON (ch.id = ac.chart_id) + WHERE ch.accno = '$form->{accno}' + $transdate + $cleared + |; + ($form->{beginningbalance}) = $dbh->selectrow_array($query); + + # fx balance + $query = qq|SELECT sum(ac.amount) + FROM acc_trans ac + JOIN chart ch ON (ch.id = ac.chart_id) + WHERE ch.accno = '$form->{accno}' + AND ac.fx_transaction = '1' + $transdate + $cleared + |; + ($form->{fx_balance}) = $dbh->selectrow_array($query); + + + $transdate = ""; + if ($form->{todate}) { + $transdate = qq| AND ac.transdate <= date '$form->{todate}'|; + } + + # get statement balance + $query = qq|SELECT sum(ac.amount) + FROM acc_trans ac + JOIN chart ch ON (ch.id = ac.chart_id) + WHERE ch.accno = '$form->{accno}' + $transdate + |; + ($form->{endingbalance}) = $dbh->selectrow_array($query); + + # fx balance + $query = qq|SELECT sum(ac.amount) + FROM acc_trans ac + JOIN chart ch ON (ch.id = ac.chart_id) + WHERE ch.accno = '$form->{accno}' + AND ac.fx_transaction = '1' + $transdate + |; + ($form->{fx_endingbalance}) = $dbh->selectrow_array($query); + + + $cleared = qq| AND ac.cleared = '0'| unless $form->{fromdate}; + + if ($form->{report}) { + $cleared = qq| AND NOT (ac.cleared = '0' OR ac.cleared = '1')|; + if ($form->{cleared}) { + $cleared = qq| AND ac.cleared = '1'|; + } + if ($form->{outstanding}) { + $cleared = ($form->{cleared}) ? "" : qq| AND ac.cleared = '0'|; + } + if (! $form->{fromdate}) { + $form->{beginningbalance} = 0; + $form->{fx_balance} = 0; + } + } + + my $fx_transaction; + if ($form->{fx_transaction}) { + $fx_transaction = qq| + AND NOT + (ac.chart_id IN + (SELECT fxgain_accno_id FROM defaults + UNION + SELECT fxloss_accno_id FROM defaults))|; + } else { + $fx_transaction = qq| + AND ac.fx_transaction = '0'|; + } + + + if ($form->{summary}) { + $query = qq|SELECT ac.transdate, ac.source, + sum(ac.amount) AS amount, ac.cleared + FROM acc_trans ac + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ch.accno = '$form->{accno}' + AND ac.amount >= 0 + $fx_transaction + $cleared|; + $query .= " AND ac.transdate >= '$form->{fromdate}'" if $form->{fromdate}; + $query .= " AND ac.transdate <= '$form->{todate}'" if $form->{todate}; + $query .= " GROUP BY ac.source, ac.transdate, ac.cleared"; + $query .= qq| + UNION ALL + SELECT ac.transdate, ac.source, + sum(ac.amount) AS amount, ac.cleared + FROM acc_trans ac + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ch.accno = '$form->{accno}' + AND ac.amount < 0 + $fx_transaction + $cleared|; + $query .= " AND ac.transdate >= '$form->{fromdate}'" if $form->{fromdate}; + $query .= " AND ac.transdate <= '$form->{todate}'" if $form->{todate}; + $query .= " GROUP BY ac.source, ac.transdate, ac.cleared"; + + $query .= " ORDER BY 1,2"; + + } else { + + $query = qq|SELECT ac.transdate, ac.source, ac.fx_transaction, + ac.amount, ac.cleared, g.id, g.description + FROM acc_trans ac + JOIN chart ch ON (ac.chart_id = ch.id) + JOIN gl g ON (g.id = ac.trans_id) + WHERE ch.accno = '$form->{accno}' + $fx_transaction + $cleared|; + $query .= " AND ac.transdate >= '$form->{fromdate}'" if $form->{fromdate}; + $query .= " AND ac.transdate <= '$form->{todate}'" if $form->{todate}; + $query .= qq| + UNION ALL + SELECT ac.transdate, ac.source, ac.fx_transaction, + ac.amount, ac.cleared, a.id, n.name + FROM acc_trans ac + JOIN chart ch ON (ac.chart_id = ch.id) + JOIN ar a ON (a.id = ac.trans_id) + JOIN customer n ON (n.id = a.customer_id) + WHERE ch.accno = '$form->{accno}' + $fx_transaction + $cleared|; + $query .= " AND ac.transdate >= '$form->{fromdate}'" if $form->{fromdate}; + $query .= " AND ac.transdate <= '$form->{todate}'" if $form->{todate}; + $query .= qq| + UNION ALL + SELECT ac.transdate, ac.source, ac.fx_transaction, + ac.amount, ac.cleared, a.id, n.name + FROM acc_trans ac + JOIN chart ch ON (ac.chart_id = ch.id) + JOIN ap a ON (a.id = ac.trans_id) + JOIN vendor n ON (n.id = a.vendor_id) + WHERE ch.accno = '$form->{accno}' + $fx_transaction + $cleared|; + $query .= " AND ac.transdate >= '$form->{fromdate}'" if $form->{fromdate}; + $query .= " AND ac.transdate <= '$form->{todate}'" if $form->{todate}; + + $query .= " ORDER BY 1,2,3"; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $dr; + my $cr; + + if ($form->{summary}) { + $query = qq|SELECT c.name + FROM customer c + JOIN ar a ON (c.id = a.customer_id) + JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ac.transdate = ? + AND ch.accno = '$form->{accno}' + AND (ac.source = ? OR ac.source IS NULL) + AND ac.amount >= 0 + $cleared + UNION + SELECT v.name + FROM vendor v + JOIN ap a ON (v.id = a.vendor_id) + JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ac.transdate = ? + AND ch.accno = '$form->{accno}' + AND (ac.source = ? OR ac.source IS NULL) + AND ac.amount > 0 + $cleared + UNION + SELECT g.description + FROM gl g + JOIN acc_trans ac ON (g.id = ac.trans_id) + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ac.transdate = ? + AND ch.accno = '$form->{accno}' + AND (ac.source = ? OR ac.source IS NULL) + AND ac.amount >= 0 + $cleared + |; + + $query .= " ORDER BY 1"; + $dr = $dbh->prepare($query); + + $query = qq|SELECT c.name + FROM customer c + JOIN ar a ON (c.id = a.customer_id) + JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ac.transdate = ? + AND ch.accno = '$form->{accno}' + AND (ac.source = ? OR ac.source IS NULL) + AND ac.amount < 0 + $cleared + UNION + SELECT v.name + FROM vendor v + JOIN ap a ON (v.id = a.vendor_id) + JOIN acc_trans ac ON (a.id = ac.trans_id) + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ac.transdate = ? + AND ch.accno = '$form->{accno}' + AND (ac.source = ? OR ac.source IS NULL) + AND ac.amount < 0 + $cleared + UNION + SELECT g.description + FROM gl g + JOIN acc_trans ac ON (g.id = ac.trans_id) + JOIN chart ch ON (ac.chart_id = ch.id) + WHERE ac.transdate = ? + AND ch.accno = '$form->{accno}' + AND (ac.source = ? OR ac.source IS NULL) + AND ac.amount < 0 + $cleared + |; + + $query .= " ORDER BY 1"; + $cr = $dbh->prepare($query); + } + + my $name; + my $ref; + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + if ($form->{summary}) { + + if ($ref->{amount} > 0) { + $dr->execute($ref->{transdate}, $ref->{source}, $ref->{transdate}, $ref->{source}, $ref->{transdate}, $ref->{source}); + $ref->{oldcleared} = $ref->{cleared}; + $ref->{name} = (); + + while (($name) = $dr->fetchrow_array) { + push @{ $ref->{name} }, $name; + } + $dr->finish; + } else { + + $cr->execute($ref->{transdate}, $ref->{source}, $ref->{transdate}, $ref->{source}, $ref->{transdate}, $ref->{source}); + $ref->{oldcleared} = $ref->{cleared}; + $ref->{name} = (); + while (($name) = $cr->fetchrow_array) { + push @{ $ref->{name} }, $name; + } + $cr->finish; + + } + + } else { + push @{ $ref->{name} }, $ref->{description}; + } + + push @{ $form->{PR} }, $ref; + + } + $sth->finish; + + $dbh->disconnect; + +} + + +sub reconcile { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT id FROM chart + WHERE accno = '$form->{accno}'|; + my ($chart_id) = $dbh->selectrow_array($query); + $chart_id *= 1; + + $query = qq|SELECT trans_id FROM acc_trans + WHERE (source = ? OR source IS NULL) + AND transdate = ? + AND cleared = '0' + AND chart_id = $chart_id|; + my $sth = $dbh->prepare($query) || $form->dberror($query); + + my $i; + my $trans_id; + + $query = qq|UPDATE acc_trans SET cleared = '1' + WHERE cleared = '0' + AND trans_id = ? + AND transdate = ? + AND chart_id = $chart_id|; + my $tth = $dbh->prepare($query) || $form->dberror($query); + + # clear flags + for $i (1 .. $form->{rowcount}) { + if ($form->{"cleared_$i"} && ! $form->{"oldcleared_$i"}) { + if ($form->{summary}) { + $sth->execute($form->{"source_$i"}, $form->{"transdate_$i"}) || $form->dberror; + + while (($trans_id) = $sth->fetchrow_array) { + $tth->execute($trans_id, $form->{"transdate_$i"}) || $form->dberror; + $tth->finish; + } + $sth->finish; + + } else { + + $tth->execute($form->{"id_$i"}, $form->{"transdate_$i"}) || $form->dberror; + $tth->finish; + } + } + } + + $dbh->disconnect; + +} + +1; + diff --git a/LedgerSMB/RP.pm b/LedgerSMB/RP.pm new file mode 100755 index 00000000..946d4c69 --- /dev/null +++ b/LedgerSMB/RP.pm @@ -0,0 +1,2103 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# backend code for reports +# +#====================================================================== + +package RP; + + +sub yearend_statement { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + # if todate < existing yearends, delete GL and yearends + my $query = qq|SELECT trans_id FROM yearend + WHERE transdate >= '$form->{todate}'|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my @trans_id = (); + my $id; + while (($id) = $sth->fetchrow_array) { + push @trans_id, $id; + } + $sth->finish; + + $query = qq|DELETE FROM gl + WHERE id = ?|; + $sth = $dbh->prepare($query) || $form->dberror($query); + + $query = qq|DELETE FROM acc_trans + WHERE trans_id = ?|; + my $ath = $dbh->prepare($query) || $form->dberror($query); + + foreach $id (@trans_id) { + $sth->execute($id); + $ath->execute($id); + + $sth->finish; + $ath->finish; + } + + + my $last_period = 0; + my @categories = qw(I E); + my $category; + + $form->{decimalplaces} *= 1; + + &get_accounts($dbh, 0, $form->{fromdate}, $form->{todate}, $form, \@categories); + + # disconnect + $dbh->disconnect; + + + # now we got $form->{I}{accno}{ } + # and $form->{E}{accno}{ } + + my %account = ( 'I' => { 'label' => 'income', + 'labels' => 'income', + 'ml' => 1 }, + 'E' => { 'label' => 'expense', + 'labels' => 'expenses', + 'ml' => -1 } + ); + + foreach $category (@categories) { + foreach $key (sort keys %{ $form->{$category} }) { + if ($form->{$category}{$key}{charttype} eq 'A') { + $form->{"total_$account{$category}{labels}_this_period"} += $form->{$category}{$key}{this} * $account{$category}{ml}; + } + } + } + + + # totals for income and expenses + $form->{total_income_this_period} = $form->round_amount($form->{total_income_this_period}, $form->{decimalplaces}); + $form->{total_expenses_this_period} = $form->round_amount($form->{total_expenses_this_period}, $form->{decimalplaces}); + + # total for income/loss + $form->{total_this_period} = $form->{total_income_this_period} - $form->{total_expenses_this_period}; + +} + + +sub income_statement { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $last_period = 0; + my @categories = qw(I E); + my $category; + + $form->{decimalplaces} *= 1; + + if (! ($form->{fromdate} || $form->{todate})) { + if ($form->{fromyear} && $form->{frommonth}) { + ($form->{fromdate}, $form->{todate}) = $form->from_to($form->{fromyear}, $form->{frommonth}, $form->{interval}); + } + } + + &get_accounts($dbh, $last_period, $form->{fromdate}, $form->{todate}, $form, \@categories, 1); + + if (! ($form->{comparefromdate} || $form->{comparetodate})) { + if ($form->{compareyear} && $form->{comparemonth}) { + ($form->{comparefromdate}, $form->{comparetodate}) = $form->from_to($form->{compareyear}, $form->{comparemonth}, $form->{interval}); + } + } + + # if there are any compare dates + if ($form->{comparefromdate} || $form->{comparetodate}) { + $last_period = 1; + + &get_accounts($dbh, $last_period, $form->{comparefromdate}, $form->{comparetodate}, $form, \@categories, 1); + } + + + # disconnect + $dbh->disconnect; + + + # now we got $form->{I}{accno}{ } + # and $form->{E}{accno}{ } + + my %account = ( 'I' => { 'label' => 'income', + 'labels' => 'income', + 'ml' => 1 }, + 'E' => { 'label' => 'expense', + 'labels' => 'expenses', + 'ml' => -1 } + ); + + my $str; + + foreach $category (@categories) { + + foreach $key (sort keys %{ $form->{$category} }) { + # push description onto array + + $str = ($form->{l_heading}) ? $form->{padding} : ""; + + if ($form->{$category}{$key}{charttype} eq "A") { + $str .= ($form->{l_accno}) ? "$form->{$category}{$key}{accno} - $form->{$category}{$key}{description}" : "$form->{$category}{$key}{description}"; + } + if ($form->{$category}{$key}{charttype} eq "H") { + if ($account{$category}{subtotal} && $form->{l_subtotal}) { + $dash = "- "; + push(@{$form->{"$account{$category}{label}_account"}}, "$str$form->{bold}$account{$category}{subdescription}$form->{endbold}"); + push(@{$form->{"$account{$category}{labels}_this_period"}}, $form->format_amount($myconfig, $account{$category}{subthis} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + + if ($last_period) { + push(@{$form->{"$account{$category}{labels}_last_period"}}, $form->format_amount($myconfig, $account{$category}{sublast} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + } + + } + + $str = "$form->{br}$form->{bold}$form->{$category}{$key}{description}$form->{endbold}"; + + $account{$category}{subthis} = $form->{$category}{$key}{this}; + $account{$category}{sublast} = $form->{$category}{$key}{last}; + $account{$category}{subdescription} = $form->{$category}{$key}{description}; + $account{$category}{subtotal} = 1; + + $form->{$category}{$key}{this} = 0; + $form->{$category}{$key}{last} = 0; + + next unless $form->{l_heading}; + + $dash = " "; + } + + push(@{$form->{"$account{$category}{label}_account"}}, $str); + + if ($form->{$category}{$key}{charttype} eq 'A') { + $form->{"total_$account{$category}{labels}_this_period"} += $form->{$category}{$key}{this} * $account{$category}{ml}; + $dash = "- "; + } + + push(@{$form->{"$account{$category}{labels}_this_period"}}, $form->format_amount($myconfig, $form->{$category}{$key}{this} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + + # add amount or - for last period + if ($last_period) { + $form->{"total_$account{$category}{labels}_last_period"} += $form->{$category}{$key}{last} * $account{$category}{ml}; + + push(@{$form->{"$account{$category}{labels}_last_period"}}, $form->format_amount($myconfig,$form->{$category}{$key}{last} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + } + } + + $str = ($form->{l_heading}) ? $form->{padding} : ""; + if ($account{$category}{subtotal} && $form->{l_subtotal}) { + push(@{$form->{"$account{$category}{label}_account"}}, "$str$form->{bold}$account{$category}{subdescription}$form->{endbold}"); + push(@{$form->{"$account{$category}{labels}_this_period"}}, $form->format_amount($myconfig, $account{$category}{subthis} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + + if ($last_period) { + push(@{$form->{"$account{$category}{labels}_last_period"}}, $form->format_amount($myconfig, $account{$category}{sublast} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + } + } + + } + + + # totals for income and expenses + $form->{total_income_this_period} = $form->round_amount($form->{total_income_this_period}, $form->{decimalplaces}); + $form->{total_expenses_this_period} = $form->round_amount($form->{total_expenses_this_period}, $form->{decimalplaces}); + + # total for income/loss + $form->{total_this_period} = $form->{total_income_this_period} - $form->{total_expenses_this_period}; + + if ($last_period) { + # total for income/loss + $form->{total_last_period} = $form->format_amount($myconfig, $form->{total_income_last_period} - $form->{total_expenses_last_period}, $form->{decimalplaces}, "- "); + + # totals for income and expenses for last_period + $form->{total_income_last_period} = $form->format_amount($myconfig, $form->{total_income_last_period}, $form->{decimalplaces}, "- "); + $form->{total_expenses_last_period} = $form->format_amount($myconfig, $form->{total_expenses_last_period}, $form->{decimalplaces}, "- "); + + } + + + $form->{total_income_this_period} = $form->format_amount($myconfig,$form->{total_income_this_period}, $form->{decimalplaces}, "- "); + $form->{total_expenses_this_period} = $form->format_amount($myconfig,$form->{total_expenses_this_period}, $form->{decimalplaces}, "- "); + $form->{total_this_period} = $form->format_amount($myconfig,$form->{total_this_period}, $form->{decimalplaces}, "- "); + +} + + +sub balance_sheet { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $last_period = 0; + my @categories = qw(A L Q); + + my $null; + + if ($form->{asofdate}) { + if ($form->{asofyear} && $form->{asofmonth}) { + if ($form->{asofdate} !~ /\W/) { + $form->{asofdate} = "$form->{asofyear}$form->{asofmonth}$form->{asofdate}"; + } + } + } else { + if ($form->{asofyear} && $form->{asofmonth}) { + ($null, $form->{asofdate}) = $form->from_to($form->{asofyear}, $form->{asofmonth}); + } + } + + # if there are any dates construct a where + if ($form->{asofdate}) { + + $form->{this_period} = "$form->{asofdate}"; + $form->{period} = "$form->{asofdate}"; + + } + + $form->{decimalplaces} *= 1; + + &get_accounts($dbh, $last_period, "", $form->{asofdate}, $form, \@categories, 1); + + if ($form->{compareasofdate}) { + if ($form->{compareasofyear} && $form->{compareasofmonth}) { + if ($form->{compareasofdate} !~ /\W/) { + $form->{compareasofdate} = "$form->{compareasofyear}$form->{compareasofmonth}$form->{compareasofdate}"; + } + } + } else { + if ($form->{compareasofyear} && $form->{compareasofmonth}) { + ($null, $form->{compareasofdate}) = $form->from_to($form->{compareasofyear}, $form->{compareasofmonth}); + } + } + + # if there are any compare dates + if ($form->{compareasofdate}) { + + $last_period = 1; + &get_accounts($dbh, $last_period, "", $form->{compareasofdate}, $form, \@categories, 1); + + $form->{last_period} = "$form->{compareasofdate}"; + + } + + + # disconnect + $dbh->disconnect; + + + # now we got $form->{A}{accno}{ } assets + # and $form->{L}{accno}{ } liabilities + # and $form->{Q}{accno}{ } equity + # build asset accounts + + my $str; + my $key; + + my %account = ( 'A' => { 'label' => 'asset', + 'labels' => 'assets', + 'ml' => -1 }, + 'L' => { 'label' => 'liability', + 'labels' => 'liabilities', + 'ml' => 1 }, + 'Q' => { 'label' => 'equity', + 'labels' => 'equity', + 'ml' => 1 } + ); + + + foreach $category (@categories) { + + foreach $key (sort keys %{ $form->{$category} }) { + + $str = ($form->{l_heading}) ? $form->{padding} : ""; + + if ($form->{$category}{$key}{charttype} eq "A") { + $str .= ($form->{l_accno}) ? "$form->{$category}{$key}{accno} - $form->{$category}{$key}{description}" : "$form->{$category}{$key}{description}"; + } + if ($form->{$category}{$key}{charttype} eq "H") { + if ($account{$category}{subtotal} && $form->{l_subtotal}) { + $dash = "- "; + push(@{$form->{"$account{$category}{label}_account"}}, "$str$form->{bold}$account{$category}{subdescription}$form->{endbold}"); + push(@{$form->{"$account{$category}{label}_this_period"}}, $form->format_amount($myconfig, $account{$category}{subthis} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + + if ($last_period) { + push(@{$form->{"$account{$category}{label}_last_period"}}, $form->format_amount($myconfig, $account{$category}{sublast} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + } + } + + $str = "$form->{bold}$form->{$category}{$key}{description}$form->{endbold}"; + + $account{$category}{subthis} = $form->{$category}{$key}{this}; + $account{$category}{sublast} = $form->{$category}{$key}{last}; + $account{$category}{subdescription} = $form->{$category}{$key}{description}; + $account{$category}{subtotal} = 1; + + $form->{$category}{$key}{this} = 0; + $form->{$category}{$key}{last} = 0; + + next unless $form->{l_heading}; + + $dash = " "; + } + + # push description onto array + push(@{$form->{"$account{$category}{label}_account"}}, $str); + + if ($form->{$category}{$key}{charttype} eq 'A') { + $form->{"total_$account{$category}{labels}_this_period"} += $form->{$category}{$key}{this} * $account{$category}{ml}; + $dash = "- "; + } + + push(@{$form->{"$account{$category}{label}_this_period"}}, $form->format_amount($myconfig, $form->{$category}{$key}{this} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + + if ($last_period) { + $form->{"total_$account{$category}{labels}_last_period"} += $form->{$category}{$key}{last} * $account{$category}{ml}; + + push(@{$form->{"$account{$category}{label}_last_period"}}, $form->format_amount($myconfig, $form->{$category}{$key}{last} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + } + } + + $str = ($form->{l_heading}) ? $form->{padding} : ""; + if ($account{$category}{subtotal} && $form->{l_subtotal}) { + push(@{$form->{"$account{$category}{label}_account"}}, "$str$form->{bold}$account{$category}{subdescription}$form->{endbold}"); + push(@{$form->{"$account{$category}{label}_this_period"}}, $form->format_amount($myconfig, $account{$category}{subthis} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + + if ($last_period) { + push(@{$form->{"$account{$category}{label}_last_period"}}, $form->format_amount($myconfig, $account{$category}{sublast} * $account{$category}{ml}, $form->{decimalplaces}, $dash)); + } + } + + } + + + # totals for assets, liabilities + $form->{total_assets_this_period} = $form->round_amount($form->{total_assets_this_period}, $form->{decimalplaces}); + $form->{total_liabilities_this_period} = $form->round_amount($form->{total_liabilities_this_period}, $form->{decimalplaces}); + $form->{total_equity_this_period} = $form->round_amount($form->{total_equity_this_period}, $form->{decimalplaces}); + + # calculate earnings + $form->{earnings_this_period} = $form->{total_assets_this_period} - $form->{total_liabilities_this_period} - $form->{total_equity_this_period}; + + push(@{$form->{equity_this_period}}, $form->format_amount($myconfig, $form->{earnings_this_period}, $form->{decimalplaces}, "- ")); + + $form->{total_equity_this_period} = $form->round_amount($form->{total_equity_this_period} + $form->{earnings_this_period}, $form->{decimalplaces}); + + # add liability + equity + $form->{total_this_period} = $form->format_amount($myconfig, $form->{total_liabilities_this_period} + $form->{total_equity_this_period}, $form->{decimalplaces}, "- "); + + + if ($last_period) { + # totals for assets, liabilities + $form->{total_assets_last_period} = $form->round_amount($form->{total_assets_last_period}, $form->{decimalplaces}); + $form->{total_liabilities_last_period} = $form->round_amount($form->{total_liabilities_last_period}, $form->{decimalplaces}); + $form->{total_equity_last_period} = $form->round_amount($form->{total_equity_last_period}, $form->{decimalplaces}); + + # calculate retained earnings + $form->{earnings_last_period} = $form->{total_assets_last_period} - $form->{total_liabilities_last_period} - $form->{total_equity_last_period}; + + push(@{$form->{equity_last_period}}, $form->format_amount($myconfig,$form->{earnings_last_period}, $form->{decimalplaces}, "- ")); + + $form->{total_equity_last_period} = $form->round_amount($form->{total_equity_last_period} + $form->{earnings_last_period}, $form->{decimalplaces}); + + # add liability + equity + $form->{total_last_period} = $form->format_amount($myconfig, $form->{total_liabilities_last_period} + $form->{total_equity_last_period}, $form->{decimalplaces}, "- "); + + } + + + $form->{total_liabilities_last_period} = $form->format_amount($myconfig, $form->{total_liabilities_last_period}, $form->{decimalplaces}, "- ") if ($form->{total_liabilities_last_period}); + + $form->{total_equity_last_period} = $form->format_amount($myconfig, $form->{total_equity_last_period}, $form->{decimalplaces}, "- ") if ($form->{total_equity_last_period}); + + $form->{total_assets_last_period} = $form->format_amount($myconfig, $form->{total_assets_last_period}, $form->{decimalplaces}, "- ") if ($form->{total_assets_last_period}); + + $form->{total_assets_this_period} = $form->format_amount($myconfig, $form->{total_assets_this_period}, $form->{decimalplaces}, "- "); + + $form->{total_liabilities_this_period} = $form->format_amount($myconfig, $form->{total_liabilities_this_period}, $form->{decimalplaces}, "- "); + + $form->{total_equity_this_period} = $form->format_amount($myconfig, $form->{total_equity_this_period}, $form->{decimalplaces}, "- "); + +} + + +sub get_accounts { + my ($dbh, $last_period, $fromdate, $todate, $form, $categories, $excludeyearend) = @_; + + my $department_id; + my $project_id; + + ($null, $department_id) = split /--/, $form->{department}; + ($null, $project_id) = split /--/, $form->{projectnumber}; + + my $query; + my $dpt_where; + my $dpt_join; + my $project; + my $where = "1 = 1"; + my $glwhere = ""; + my $subwhere = ""; + my $yearendwhere = "1 = 1"; + my $item; + + my $category = "AND ("; + foreach $item (@{ $categories }) { + $category .= qq|c.category = '$item' OR |; + } + $category =~ s/OR $/\)/; + + + # get headings + $query = qq|SELECT accno, description, category + FROM chart c + WHERE c.charttype = 'H' + $category + ORDER by c.accno|; + + if ($form->{accounttype} eq 'gifi') + { + $query = qq|SELECT g.accno, g.description, c.category + FROM gifi g + JOIN chart c ON (c.gifi_accno = g.accno) + WHERE c.charttype = 'H' + $category + ORDER BY g.accno|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my @headingaccounts = (); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) + { + $form->{$ref->{category}}{$ref->{accno}}{description} = "$ref->{description}"; + $form->{$ref->{category}}{$ref->{accno}}{charttype} = "H"; + $form->{$ref->{category}}{$ref->{accno}}{accno} = $ref->{accno}; + + push @headingaccounts, $ref->{accno}; + } + + $sth->finish; + + if ($form->{method} eq 'cash' && !$todate) { + ($todate) = $dbh->selectrow_array(qq|SELECT current_date FROM defaults|); + } + + if ($fromdate) { + if ($form->{method} eq 'cash') { + $subwhere .= " AND transdate >= '$fromdate'"; + $glwhere = " AND ac.transdate >= '$fromdate'"; + } else { + $where .= " AND ac.transdate >= '$fromdate'"; + } + } + + if ($todate) { + $where .= " AND ac.transdate <= '$todate'"; + $subwhere .= " AND transdate <= '$todate'"; + $yearendwhere = "ac.transdate < '$todate'"; + } + + if ($excludeyearend) { + $ywhere = " AND ac.trans_id NOT IN + (SELECT trans_id FROM yearend)"; + + if ($todate) { + $ywhere = " AND ac.trans_id NOT IN + (SELECT trans_id FROM yearend + WHERE transdate <= '$todate')"; + } + + if ($fromdate) { + $ywhere = " AND ac.trans_id NOT IN + (SELECT trans_id FROM yearend + WHERE transdate >= '$fromdate')"; + if ($todate) { + $ywhere = " AND ac.trans_id NOT IN + (SELECT trans_id FROM yearend + WHERE transdate >= '$fromdate' + AND transdate <= '$todate')"; + } + } + } + + if ($department_id) { + $dpt_join = qq| + JOIN department t ON (a.department_id = t.id) + |; + $dpt_where = qq| + AND t.id = $department_id + |; + } + + if ($project_id) { + $project = qq| + AND ac.project_id = $project_id + |; + } + + + if ($form->{accounttype} eq 'gifi') { + + if ($form->{method} eq 'cash') { + + $query = qq| + + SELECT g.accno, sum(ac.amount) AS amount, + g.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN ar a ON (a.id = ac.trans_id) + JOIN gifi g ON (g.accno = c.gifi_accno) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = id) + WHERE link LIKE '%AR_paid%' + $subwhere + ) + $project + GROUP BY g.accno, g.description, c.category + + UNION ALL + + SELECT '' AS accno, SUM(ac.amount) AS amount, + '' AS description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN ar a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND c.gifi_accno = '' + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = id) + WHERE link LIKE '%AR_paid%' + $subwhere + ) + $project + GROUP BY c.category + + UNION ALL + + SELECT g.accno, sum(ac.amount) AS amount, + g.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN ap a ON (a.id = ac.trans_id) + JOIN gifi g ON (g.accno = c.gifi_accno) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = id) + WHERE link LIKE '%AP_paid%' + $subwhere + ) + $project + GROUP BY g.accno, g.description, c.category + + UNION ALL + + SELECT '' AS accno, SUM(ac.amount) AS amount, + '' AS description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN ap a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND c.gifi_accno = '' + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = id) + WHERE link LIKE '%AP_paid%' + $subwhere + ) + $project + GROUP BY c.category + + UNION ALL + +-- add gl + + SELECT g.accno, sum(ac.amount) AS amount, + g.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN gifi g ON (g.accno = c.gifi_accno) + JOIN gl a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $glwhere + $dpt_where + $category + AND NOT (c.link = 'AR' OR c.link = 'AP') + $project + GROUP BY g.accno, g.description, c.category + + UNION ALL + + SELECT '' AS accno, SUM(ac.amount) AS amount, + '' AS description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN gl a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $glwhere + $dpt_where + $category + AND c.gifi_accno = '' + AND NOT (c.link = 'AR' OR c.link = 'AP') + $project + GROUP BY c.category + |; + + if ($excludeyearend) { + + # this is for the yearend + + $query .= qq| + + UNION ALL + + SELECT g.accno, sum(ac.amount) AS amount, + g.description, c.category + FROM yearend y + JOIN gl a ON (a.id = y.trans_id) + JOIN acc_trans ac ON (ac.trans_id = y.trans_id) + JOIN chart c ON (c.id = ac.chart_id) + JOIN gifi g ON (g.accno = c.gifi_accno) + $dpt_join + WHERE $yearendwhere + AND c.category = 'Q' + $dpt_where + $project + GROUP BY g.accno, g.description, c.category + |; + } + + } else { + + if ($department_id) { + $dpt_join = qq| + JOIN dpt_trans t ON (t.trans_id = ac.trans_id) + |; + $dpt_where = qq| + AND t.department_id = $department_id + |; + } + + $query = qq| + + SELECT g.accno, SUM(ac.amount) AS amount, + g.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN gifi g ON (c.gifi_accno = g.accno) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + $project + GROUP BY g.accno, g.description, c.category + + UNION ALL + + SELECT '' AS accno, SUM(ac.amount) AS amount, + '' AS description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND c.gifi_accno = '' + $project + GROUP BY c.category + |; + + if ($excludeyearend) { + + # this is for the yearend + + $query .= qq| + + UNION ALL + + SELECT g.accno, sum(ac.amount) AS amount, + g.description, c.category + FROM yearend y + JOIN gl a ON (a.id = y.trans_id) + JOIN acc_trans ac ON (ac.trans_id = y.trans_id) + JOIN chart c ON (c.id = ac.chart_id) + JOIN gifi g ON (g.accno = c.gifi_accno) + $dpt_join + WHERE $yearendwhere + AND c.category = 'Q' + $dpt_where + $project + GROUP BY g.accno, g.description, c.category + |; + } + } + + } else { # standard account + + if ($form->{method} eq 'cash') { + + $query = qq| + + SELECT c.accno, sum(ac.amount) AS amount, + c.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN ar a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = id) + WHERE link LIKE '%AR_paid%' + $subwhere + ) + + $project + GROUP BY c.accno, c.description, c.category + + UNION ALL + + SELECT c.accno, sum(ac.amount) AS amount, + c.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN ap a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = id) + WHERE link LIKE '%AP_paid%' + $subwhere + ) + + $project + GROUP BY c.accno, c.description, c.category + + UNION ALL + + SELECT c.accno, sum(ac.amount) AS amount, + c.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN gl a ON (a.id = ac.trans_id) + $dpt_join + WHERE $where + $ywhere + $glwhere + $dpt_where + $category + AND NOT (c.link = 'AR' OR c.link = 'AP') + $project + GROUP BY c.accno, c.description, c.category + |; + + if ($excludeyearend) { + + # this is for the yearend + + $query .= qq| + + UNION ALL + + SELECT c.accno, sum(ac.amount) AS amount, + c.description, c.category + FROM yearend y + JOIN gl a ON (a.id = y.trans_id) + JOIN acc_trans ac ON (ac.trans_id = y.trans_id) + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $yearendwhere + AND c.category = 'Q' + $dpt_where + $project + GROUP BY c.accno, c.description, c.category + |; + } + + } else { + + if ($department_id) { + $dpt_join = qq| + JOIN dpt_trans t ON (t.trans_id = ac.trans_id) + |; + $dpt_where = qq| + AND t.department_id = $department_id + |; + } + + + $query = qq| + + SELECT c.accno, sum(ac.amount) AS amount, + c.description, c.category + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $ywhere + $dpt_where + $category + $project + GROUP BY c.accno, c.description, c.category + |; + + if ($excludeyearend) { + + # this is for the yearend + + $query .= qq| + + UNION ALL + + SELECT c.accno, sum(ac.amount) AS amount, + c.description, c.category + FROM yearend y + JOIN gl a ON (a.id = y.trans_id) + JOIN acc_trans ac ON (ac.trans_id = y.trans_id) + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $yearendwhere + AND c.category = 'Q' + $dpt_where + $project + GROUP BY c.accno, c.description, c.category + |; + } + } + } + + my @accno; + my $accno; + my $ref; + + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + + # get last heading account + @accno = grep { $_ le "$ref->{accno}" } @headingaccounts; + $accno = pop @accno; + if ($accno && ($accno ne $ref->{accno}) ) { + if ($last_period) + { + $form->{$ref->{category}}{$accno}{last} += $ref->{amount}; + } else { + $form->{$ref->{category}}{$accno}{this} += $ref->{amount}; + } + } + + $form->{$ref->{category}}{$ref->{accno}}{accno} = $ref->{accno}; + $form->{$ref->{category}}{$ref->{accno}}{description} = $ref->{description}; + $form->{$ref->{category}}{$ref->{accno}}{charttype} = "A"; + + if ($last_period) { + $form->{$ref->{category}}{$ref->{accno}}{last} += $ref->{amount}; + } else { + $form->{$ref->{category}}{$ref->{accno}}{this} += $ref->{amount}; + } + } + $sth->finish; + + + # remove accounts with zero balance + foreach $category (@{ $categories }) { + foreach $accno (keys %{ $form->{$category} }) { + $form->{$category}{$accno}{last} = $form->round_amount($form->{$category}{$accno}{last}, $form->{decimalplaces}); + $form->{$category}{$accno}{this} = $form->round_amount($form->{$category}{$accno}{this}, $form->{decimalplaces}); + + delete $form->{$category}{$accno} if ($form->{$category}{$accno}{this} == 0 && $form->{$category}{$accno}{last} == 0); + } + } + +} + + + +sub trial_balance { + my ($self, $myconfig, $form) = @_; + + my $dbh = $form->dbconnect($myconfig); + + my ($query, $sth, $ref); + my %balance = (); + my %trb = (); + my $null; + my $department_id; + my $project_id; + my @headingaccounts = (); + my $dpt_where; + my $dpt_join; + my $project; + + my $where = "1 = 1"; + my $invwhere = $where; + + ($null, $department_id) = split /--/, $form->{department}; + ($null, $project_id) = split /--/, $form->{projectnumber}; + + if ($department_id) { + $dpt_join = qq| + JOIN dpt_trans t ON (ac.trans_id = t.trans_id) + |; + $dpt_where = qq| + AND t.department_id = $department_id + |; + } + + + if ($project_id) { + $project = qq| + AND ac.project_id = $project_id + |; + } + + ($form->{fromdate}, $form->{todate}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + # get beginning balances + if ($form->{fromdate}) { + + if ($form->{accounttype} eq 'gifi') { + + $query = qq|SELECT g.accno, c.category, SUM(ac.amount) AS amount, + g.description, c.contra + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + JOIN gifi g ON (c.gifi_accno = g.accno) + $dpt_join + WHERE ac.transdate < '$form->{fromdate}' + $dpt_where + $project + GROUP BY g.accno, c.category, g.description, c.contra + |; + + } else { + + $query = qq|SELECT c.accno, c.category, SUM(ac.amount) AS amount, + c.description, c.contra + FROM acc_trans ac + JOIN chart c ON (ac.chart_id = c.id) + $dpt_join + WHERE ac.transdate < '$form->{fromdate}' + $dpt_where + $project + GROUP BY c.accno, c.category, c.description, c.contra + |; + + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{amount} = $form->round_amount($ref->{amount}, 2); + $balance{$ref->{accno}} = $ref->{amount}; + + if ($form->{all_accounts}) { + $trb{$ref->{accno}}{description} = $ref->{description}; + $trb{$ref->{accno}}{charttype} = 'A'; + $trb{$ref->{accno}}{category} = $ref->{category}; + $trb{$ref->{accno}}{contra} = $ref->{contra}; + } + + } + $sth->finish; + + } + + + # get headings + $query = qq|SELECT c.accno, c.description, c.category + FROM chart c + WHERE c.charttype = 'H' + ORDER by c.accno|; + + if ($form->{accounttype} eq 'gifi') + { + $query = qq|SELECT g.accno, g.description, c.category, c.contra + FROM gifi g + JOIN chart c ON (c.gifi_accno = g.accno) + WHERE c.charttype = 'H' + ORDER BY g.accno|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) + { + $trb{$ref->{accno}}{description} = $ref->{description}; + $trb{$ref->{accno}}{charttype} = 'H'; + $trb{$ref->{accno}}{category} = $ref->{category}; + $trb{$ref->{accno}}{contra} = $ref->{contra}; + + push @headingaccounts, $ref->{accno}; + } + + $sth->finish; + + + if ($form->{fromdate} || $form->{todate}) { + if ($form->{fromdate}) { + $where .= " AND ac.transdate >= '$form->{fromdate}'"; + $invwhere .= " AND a.transdate >= '$form->{fromdate}'"; + } + if ($form->{todate}) { + $where .= " AND ac.transdate <= '$form->{todate}'"; + $invwhere .= " AND a.transdate <= '$form->{todate}'"; + } + } + + + if ($form->{accounttype} eq 'gifi') { + + $query = qq|SELECT g.accno, g.description, c.category, + SUM(ac.amount) AS amount, c.contra + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + JOIN gifi g ON (c.gifi_accno = g.accno) + $dpt_join + WHERE $where + $dpt_where + $project + GROUP BY g.accno, g.description, c.category, c.contra + ORDER BY accno|; + + } else { + + $query = qq|SELECT c.accno, c.description, c.category, + SUM(ac.amount) AS amount, c.contra + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $dpt_where + $project + GROUP BY c.accno, c.description, c.category, c.contra + ORDER BY accno|; + + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + # prepare query for each account + $query = qq|SELECT (SELECT SUM(ac.amount) * -1 + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $dpt_where + $project + AND ac.amount < 0 + AND c.accno = ?) AS debit, + + (SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $dpt_where + $project + AND ac.amount > 0 + AND c.accno = ?) AS credit + |; + + if ($form->{accounttype} eq 'gifi') { + + $query = qq|SELECT (SELECT SUM(ac.amount) * -1 + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $dpt_where + $project + AND ac.amount < 0 + AND c.gifi_accno = ?) AS debit, + + (SELECT SUM(ac.amount) + FROM acc_trans ac + JOIN chart c ON (c.id = ac.chart_id) + $dpt_join + WHERE $where + $dpt_where + $project + AND ac.amount > 0 + AND c.gifi_accno = ?) AS credit|; + + } + + $drcr = $dbh->prepare($query); + + # calculate debit and credit for the period + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $trb{$ref->{accno}}{description} = $ref->{description}; + $trb{$ref->{accno}}{charttype} = 'A'; + $trb{$ref->{accno}}{category} = $ref->{category}; + $trb{$ref->{accno}}{contra} = $ref->{contra}; + $trb{$ref->{accno}}{amount} += $ref->{amount}; + } + $sth->finish; + + my ($debit, $credit); + + foreach my $accno (sort keys %trb) { + $ref = (); + + $ref->{accno} = $accno; + for (qw(description category contra charttype amount)) { $ref->{$_} = $trb{$accno}{$_} } + + $ref->{balance} = $balance{$ref->{accno}}; + + if ($trb{$accno}{charttype} eq 'A') { + if ($project_id) { + + if ($ref->{amount} < 0) { + $ref->{debit} = $ref->{amount} * -1; + } else { + $ref->{credit} = $ref->{amount}; + } + next if $form->round_amount($ref->{amount}, 2) == 0; + + } else { + + # get DR/CR + $drcr->execute($ref->{accno}, $ref->{accno}); + + ($debit, $credit) = (0,0); + while (($debit, $credit) = $drcr->fetchrow_array) { + $ref->{debit} += $debit; + $ref->{credit} += $credit; + } + $drcr->finish; + + } + + $ref->{debit} = $form->round_amount($ref->{debit}, 2); + $ref->{credit} = $form->round_amount($ref->{credit}, 2); + + if (!$form->{all_accounts}) { + next if $form->round_amount($ref->{debit} + $ref->{credit}, 2) == 0; + } + } + + # add subtotal + @accno = grep { $_ le "$ref->{accno}" } @headingaccounts; + $accno = pop @accno; + if ($accno) { + $trb{$accno}{debit} += $ref->{debit}; + $trb{$accno}{credit} += $ref->{credit}; + } + + push @{ $form->{TB} }, $ref; + + } + + $dbh->disconnect; + + # debits and credits for headings + foreach $accno (@headingaccounts) { + foreach $ref (@{ $form->{TB} }) { + if ($accno eq $ref->{accno}) { + $ref->{debit} = $trb{$accno}{debit}; + $ref->{credit} = $trb{$accno}{credit}; + } + } + } + +} + + +sub aging { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + my $invoice = ($form->{arap} eq 'ar') ? 'is' : 'ir'; + + my $query = qq|SELECT curr FROM defaults|; + ($form->{currencies}) = $dbh->selectrow_array($query); + + ($null, $form->{todate}) = $form->from_to($form->{year}, $form->{month}) if $form->{year} && $form->{month}; + + if (! $form->{todate}) { + $query = qq|SELECT current_date FROM defaults|; + ($form->{todate}) = $dbh->selectrow_array($query); + } + + my $where = "1 = 1"; + my $name; + my $null; + my $ref; + my $transdate = ($form->{overdue}) ? "duedate" : "transdate"; + + if ($form->{"$form->{ct}_id"}) { + $where .= qq| AND ct.id = $form->{"$form->{ct}_id"}|; + } else { + if ($form->{$form->{ct}} ne "") { + $name = $form->like(lc $form->{$form->{ct}}); + $where .= qq| AND lower(ct.name) LIKE '$name'| if $form->{$form->{ct}}; + } + } + + if ($form->{department}) { + ($null, $department_id) = split /--/, $form->{department}; + $where .= qq| AND a.department_id = $department_id|; + } + + # select outstanding vendors or customers, depends on $ct + $query = qq|SELECT DISTINCT ct.id, ct.name, ct.language_code + FROM $form->{ct} ct + JOIN $form->{arap} a ON (a.$form->{ct}_id = ct.id) + WHERE $where + AND a.paid != a.amount + AND (a.$transdate <= '$form->{todate}') + ORDER BY ct.name|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror; + + my @ot = (); + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @ot, $ref; + } + $sth->finish; + + my $buysell = ($form->{arap} eq 'ar') ? 'buy' : 'sell'; + + my %interval = ( 'Pg' => { + 'c0' => "(date '$form->{todate}' - interval '0 days')", + 'c30' => "(date '$form->{todate}' - interval '30 days')", + 'c60' => "(date '$form->{todate}' - interval '60 days')", + 'c90' => "(date '$form->{todate}' - interval '90 days')" }, + 'DB2' => { + 'c0' => "(date ('$form->{todate}') - 0 days)", + 'c30' => "(date ('$form->{todate}') - 30 days)", + 'c60' => "(date ('$form->{todate}') - 60 days)", + 'c90' => "(date ('$form->{todate}') - 90 days)" } + ); + + $interval{Oracle} = $interval{PgPP} = $interval{Pg}; + + + # for each company that has some stuff outstanding + $form->{currencies} ||= ":"; + + + $where = qq| + a.paid != a.amount + AND c.id = ? + AND a.curr = ?|; + + if ($department_id) { + $where .= qq| AND a.department_id = $department_id|; + } + + $query = ""; + my $union = ""; + + if ($form->{c0}) { + $query .= qq| + SELECT c.id AS ctid, c.$form->{ct}number, c.name, + c.address1, c.address2, c.city, c.state, c.zipcode, c.country, + c.contact, c.email, + c.phone as $form->{ct}phone, c.fax as $form->{ct}fax, + c.$form->{ct}number, c.taxnumber as $form->{ct}taxnumber, + a.invnumber, a.transdate, a.till, a.ordnumber, a.ponumber, a.notes, + (a.amount - a.paid) as c0, 0.00 as c30, 0.00 as c60, 0.00 as c90, + a.duedate, a.invoice, a.id, a.curr, + (SELECT $buysell FROM exchangerate e + WHERE a.curr = e.curr + AND e.transdate = a.transdate) AS exchangerate + FROM $form->{arap} a + JOIN $form->{ct} c ON (a.$form->{ct}_id = c.id) + WHERE $where + AND ( + a.$transdate <= $interval{$myconfig->{dbdriver}}{c0} + AND a.$transdate >= $interval{$myconfig->{dbdriver}}{c30} + ) +|; + + $union = qq| + UNION +|; + + } + + if ($form->{c30}) { + + $query .= qq| + + $union + + SELECT c.id AS ctid, c.$form->{ct}number, c.name, + c.address1, c.address2, c.city, c.state, c.zipcode, c.country, + c.contact, c.email, + c.phone as $form->{ct}phone, c.fax as $form->{ct}fax, + c.$form->{ct}number, c.taxnumber as $form->{ct}taxnumber, + a.invnumber, a.transdate, a.till, a.ordnumber, a.ponumber, a.notes, + 0.00 as c0, (a.amount - a.paid) as c30, 0.00 as c60, 0.00 as c90, + a.duedate, a.invoice, a.id, a.curr, + (SELECT $buysell FROM exchangerate e + WHERE a.curr = e.curr + AND e.transdate = a.transdate) AS exchangerate + FROM $form->{arap} a + JOIN $form->{ct} c ON (a.$form->{ct}_id = c.id) + WHERE $where + AND ( + a.$transdate < $interval{$myconfig->{dbdriver}}{c30} + AND a.$transdate >= $interval{$myconfig->{dbdriver}}{c60} + ) +|; + + $union = qq| + UNION +|; + + } + + if ($form->{c60}) { + + $query .= qq| + + $union + + SELECT c.id AS ctid, c.$form->{ct}number, c.name, + c.address1, c.address2, c.city, c.state, c.zipcode, c.country, + c.contact, c.email, + c.phone as $form->{ct}phone, c.fax as $form->{ct}fax, + c.$form->{ct}number, c.taxnumber as $form->{ct}taxnumber, + a.invnumber, a.transdate, a.till, a.ordnumber, a.ponumber, a.notes, + 0.00 as c0, 0.00 as c30, (a.amount - a.paid) as c60, 0.00 as c90, + a.duedate, a.invoice, a.id, a.curr, + (SELECT $buysell FROM exchangerate e + WHERE a.curr = e.curr + AND e.transdate = a.transdate) AS exchangerate + FROM $form->{arap} a + JOIN $form->{ct} c ON (a.$form->{ct}_id = c.id) + WHERE $where + AND ( + a.$transdate < $interval{$myconfig->{dbdriver}}{c60} + AND a.$transdate >= $interval{$myconfig->{dbdriver}}{c90} + ) +|; + + $union = qq| + UNION +|; + + } + + if ($form->{c90}) { + + $query .= qq| + + $union + + SELECT c.id AS ctid, c.$form->{ct}number, c.name, + c.address1, c.address2, c.city, c.state, c.zipcode, c.country, + c.contact, c.email, + c.phone as $form->{ct}phone, c.fax as $form->{ct}fax, + c.$form->{ct}number, c.taxnumber as $form->{ct}taxnumber, + a.invnumber, a.transdate, a.till, a.ordnumber, a.ponumber, a.notes, + 0.00 as c0, 0.00 as c30, 0.00 as c60, (a.amount - a.paid) as c90, + a.duedate, a.invoice, a.id, a.curr, + (SELECT $buysell FROM exchangerate e + WHERE a.curr = e.curr + AND e.transdate = a.transdate) AS exchangerate + FROM $form->{arap} a + JOIN $form->{ct} c ON (a.$form->{ct}_id = c.id) + WHERE $where + AND a.$transdate < $interval{$myconfig->{dbdriver}}{c90} +|; + } + + $query .= qq| + + ORDER BY ctid, $transdate, invnumber|; + + $sth = $dbh->prepare($query) || $form->dberror($query); + + my @var = (); + + if ($form->{c0} + $form->{c30} + $form->{c60} + $form->{c90}) { + foreach $curr (split /:/, $form->{currencies}) { + + foreach $item (@ot) { + + @var = (); + for (qw(c0 c30 c60 c90)) { push @var, ($item->{id}, $curr) if $form->{$_} } + + $sth->execute(@var); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{module} = ($ref->{invoice}) ? $invoice : $form->{arap}; + $ref->{module} = 'ps' if $ref->{till}; + $ref->{exchangerate} = 1 unless $ref->{exchangerate}; + $ref->{language_code} = $item->{language_code}; + push @{ $form->{AG} }, $ref; + } + $sth->finish; + + } + } + } + + # get language + my $query = qq|SELECT * + FROM language + ORDER BY 2|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ($ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{all_language} }, $ref; + } + $sth->finish; + + # disconnect + $dbh->disconnect; + +} + + +sub get_customer { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my $query = qq|SELECT name, email, cc, bcc + FROM $form->{ct} ct + WHERE ct.id = $form->{"$form->{ct}_id"}|; + ($form->{$form->{ct}}, $form->{email}, $form->{cc}, $form->{bcc}) = $dbh->selectrow_array($query); + + $dbh->disconnect; + +} + + +sub get_taxaccounts { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + my $ARAP = uc $form->{db}; + + # get tax accounts + my $query = qq|SELECT DISTINCT c.accno, c.description + FROM chart c + JOIN tax t ON (c.id = t.chart_id) + WHERE c.link LIKE '%${ARAP}_tax%' + ORDER BY c.accno|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror; + + my $ref = (); + while ($ref = $sth->fetchrow_hashref(NAME_lc) ) { + push @{ $form->{taxaccounts} }, $ref; + } + $sth->finish; + + # get gifi tax accounts + my $query = qq|SELECT DISTINCT g.accno, g.description + FROM gifi g + JOIN chart c ON (c.gifi_accno= g.accno) + JOIN tax t ON (c.id = t.chart_id) + WHERE c.link LIKE '%${ARAP}_tax%' + ORDER BY accno|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror; + + while ($ref = $sth->fetchrow_hashref(NAME_lc) ) { + push @{ $form->{gifi_taxaccounts} }, $ref; + } + $sth->finish; + + $dbh->disconnect; + +} + + + +sub tax_report { + my ($self, $myconfig, $form) = @_; + + # connect to database + my $dbh = $form->dbconnect($myconfig); + + my ($null, $department_id) = split /--/, $form->{department}; + + # build WHERE + my $where = "1 = 1"; + my $cashwhere = ""; + + if ($department_id) { + $where .= qq| + AND a.department_id = $department_id + |; + } + + my $query; + my $sth; + my $accno; + + if ($form->{accno}) { + if ($form->{accno} =~ /^gifi_/) { + ($null, $accno) = split /_/, $form->{accno}; + $accno = qq| AND ch.gifi_accno = '$accno'|; + } else { + $accno = $form->{accno}; + $accno = qq| AND ch.accno = '$accno'|; + } + } + + my $table; + my $ARAP; + + if ($form->{db} eq 'ar') { + $table = "customer"; + $ARAP = "AR"; + } + if ($form->{db} eq 'ap') { + $table = "vendor"; + $ARAP = "AP"; + } + + my $transdate = "a.transdate"; + + ($form->{fromdate}, $form->{todate}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + # if there are any dates construct a where + if ($form->{fromdate} || $form->{todate}) { + if ($form->{fromdate}) { + $where .= " AND $transdate >= '$form->{fromdate}'"; + } + if ($form->{todate}) { + $where .= " AND $transdate <= '$form->{todate}'"; + } + } + + + if ($form->{method} eq 'cash') { + $transdate = "a.datepaid"; + + my $todate = $form->{todate}; + if (! $todate) { + ($todate) = $dbh->selectrow_array(qq|SELECT current_date FROM defaults|); + } + + $cashwhere = qq| + AND ac.trans_id IN + ( + SELECT trans_id + FROM acc_trans + JOIN chart ON (chart_id = chart.id) + WHERE link LIKE '%${ARAP}_paid%' + AND $transdate <= '$todate' + AND a.paid = a.amount + ) + |; + + } + + + my $ml = ($form->{db} eq 'ar') ? 1 : -1; + + my %ordinal = ( 'transdate' => 3, + 'invnumber' => 4, + 'name' => 5 + ); + + my @a = qw(transdate invnumber name); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + if ($form->{summary}) { + + $query = qq|SELECT a.id, a.invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, + ac.amount * $ml AS tax, + a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN chart ch ON (ch.id = ac.chart_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE $where + $accno + $cashwhere + |; + + if ($form->{fromdate}) { + # include open transactions from previous period + if ($cashwhere) { + $query .= qq| + UNION + + SELECT a.id, a.invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, + ac.amount * $ml AS tax, + a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN chart ch ON (ch.id = ac.chart_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE a.datepaid >= '$form->{fromdate}' + $accno + $cashwhere + |; + } + } + + + } else { + + $query = qq|SELECT a.id, '0' AS invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, + ac.amount * $ml AS tax, + a.notes AS description, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN chart ch ON (ch.id = ac.chart_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE $where + $accno + AND a.invoice = '0' + $cashwhere + + UNION + + SELECT a.id, '1' AS invoice, $transdate AS transdate, + a.invnumber, n.name, + i.sellprice * i.qty * $ml AS netamount, + i.sellprice * i.qty * $ml * + (SELECT tx.rate FROM tax tx WHERE tx.chart_id = ch.id AND (tx.validto > $transdate OR tx.validto IS NULL) ORDER BY validto LIMIT 1) AS tax, + i.description, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN chart ch ON (ch.id = ac.chart_id) + JOIN $table n ON (n.id = a.${table}_id) + JOIN ${table}tax t ON (t.${table}_id = n.id AND t.chart_id = ch.id) + JOIN invoice i ON (i.trans_id = a.id) + JOIN partstax pt ON (pt.parts_id = i.parts_id AND pt.chart_id = ch.id) + WHERE $where + $accno + AND a.invoice = '1' + $cashwhere + |; + + if ($form->{fromdate}) { + if ($cashwhere) { + $query .= qq| + UNION + + SELECT a.id, '0' AS invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, + ac.amount * $ml AS tax, + a.notes AS description, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN chart ch ON (ch.id = ac.chart_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE a.datepaid >= '$form->{fromdate}' + $accno + AND a.invoice = '0' + $cashwhere + + UNION + + SELECT a.id, '1' AS invoice, $transdate AS transdate, + a.invnumber, n.name, + i.sellprice * i.qty * $ml AS netamount, + i.sellprice * i.qty * $ml * + (SELECT tx.rate FROM tax tx WHERE tx.chart_id = ch.id AND (tx.validto > $transdate OR tx.validto IS NULL) ORDER BY validto LIMIT 1) AS tax, + i.description, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN chart ch ON (ch.id = ac.chart_id) + JOIN $table n ON (n.id = a.${table}_id) + JOIN ${table}tax t ON (t.${table}_id = n.id AND t.chart_id = ch.id) + JOIN invoice i ON (i.trans_id = a.id) + JOIN partstax pt ON (pt.parts_id = i.parts_id AND pt.chart_id = ch.id) + WHERE a.datepaid >= '$form->{fromdate}' + $accno + AND a.invoice = '1' + $cashwhere + |; + } + } + } + + + if ($form->{report} =~ /nontaxable/) { + + if ($form->{summary}) { + # only gather up non-taxable transactions + $query = qq|SELECT DISTINCT a.id, a.invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE $where + AND a.netamount = a.amount + $cashwhere + |; + + if ($form->{fromdate}) { + if ($cashwhere) { + $query .= qq| + UNION + + SELECT DISTINCT a.id, a.invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE a.datepaid >= '$form->{fromdate}' + AND a.netamount = a.amount + $cashwhere + |; + } + } + + } else { + + # gather up details for non-taxable transactions + $query = qq|SELECT a.id, '0' AS invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, + a.notes AS description, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE $where + AND a.invoice = '0' + AND a.netamount = a.amount + $cashwhere + GROUP BY a.id, $transdate, a.invnumber, n.name, a.netamount, + a.notes, a.till + + UNION + + SELECT a.id, '1' AS invoice, $transdate AS transdate, + a.invnumber, n.name, + sum(ac.sellprice * ac.qty) * $ml AS netamount, + ac.description, a.till + FROM invoice ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE $where + AND a.invoice = '1' + AND ( + a.${table}_id NOT IN ( + SELECT ${table}_id FROM ${table}tax t (${table}_id) + ) OR + ac.parts_id NOT IN ( + SELECT parts_id FROM partstax p (parts_id) + ) + ) + $cashwhere + GROUP BY a.id, a.invnumber, $transdate, n.name, + ac.description, a.till + |; + + if ($form->{fromdate}) { + if ($cashwhere) { + $query .= qq| + UNION + + SELECT a.id, '0' AS invoice, $transdate AS transdate, + a.invnumber, n.name, a.netamount, + a.notes AS description, a.till + FROM acc_trans ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE a.datepaid >= '$form->{fromdate}' + AND a.invoice = '0' + AND a.netamount = a.amount + $cashwhere + GROUP BY a.id, $transdate, a.invnumber, n.name, a.netamount, + a.notes, a.till + + UNION + + SELECT a.id, '1' AS invoice, $transdate AS transdate, + a.invnumber, n.name, + sum(ac.sellprice * ac.qty) * $ml AS netamount, + ac.description, a.till + FROM invoice ac + JOIN $form->{db} a ON (a.id = ac.trans_id) + JOIN $table n ON (n.id = a.${table}_id) + WHERE a.datepaid >= '$form->{fromdate}' + AND a.invoice = '1' + AND ( + a.${table}_id NOT IN ( + SELECT ${table}_id FROM ${table}tax t (${table}_id) + ) OR + ac.parts_id NOT IN ( + SELECT parts_id FROM partstax p (parts_id) + ) + ) + $cashwhere + GROUP BY a.id, a.invnumber, $transdate, n.name, + ac.description, a.till + |; + } + } + + } + } + + + $query .= qq| + ORDER by $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while ( my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $ref->{tax} = $form->round_amount($ref->{tax}, 2); + if ($form->{report} =~ /nontaxable/) { + push @{ $form->{TR} }, $ref if $ref->{netamount}; + } else { + push @{ $form->{TR} }, $ref if $ref->{tax}; + } + } + + $sth->finish; + $dbh->disconnect; + +} + + +sub paymentaccounts { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $ARAP = uc $form->{db}; + + # get A(R|P)_paid accounts + my $query = qq|SELECT accno, description + FROM chart + WHERE link LIKE '%${ARAP}_paid%' + ORDER BY accno|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{PR} }, $ref; + } + $sth->finish; + + $form->all_years($myconfig, $dbh); + + $dbh->disconnect; + +} + + +sub payments { + my ($self, $myconfig, $form) = @_; + + # connect to database, turn AutoCommit off + my $dbh = $form->dbconnect_noauto($myconfig); + + my $ml = 1; + if ($form->{db} eq 'ar') { + $table = 'customer'; + $ml = -1; + } + if ($form->{db} eq 'ap') { + $table = 'vendor'; + } + + + my $query; + my $sth; + my $dpt_join; + my $where; + my $var; + + if ($form->{department_id}) { + $dpt_join = qq| + JOIN dpt_trans t ON (t.trans_id = ac.trans_id) + |; + + $where = qq| + AND t.department_id = $form->{department_id} + |; + } + + ($form->{fromdate}, $form->{todate}) = $form->from_to($form->{year}, $form->{month}, $form->{interval}) if $form->{year} && $form->{month}; + + if ($form->{fromdate}) { + $where .= " AND ac.transdate >= '$form->{fromdate}'"; + } + if ($form->{todate}) { + $where .= " AND ac.transdate <= '$form->{todate}'"; + } + if (!$form->{fx_transaction}) { + $where .= " AND ac.fx_transaction = '0'"; + } + + if ($form->{description} ne "") { + $var = $form->like(lc $form->{description}); + $where .= " AND lower(c.name) LIKE '$var'"; + } + if ($form->{source} ne "") { + $var = $form->like(lc $form->{source}); + $where .= " AND lower(ac.source) LIKE '$var'"; + } + if ($form->{memo} ne "") { + $var = $form->like(lc $form->{memo}); + $where .= " AND lower(ac.memo) LIKE '$var'"; + } + + my %ordinal = ( 'name' => 1, + 'transdate' => 2, + 'source' => 4, + 'employee' => 6, + 'till' => 7 + ); + + my @a = qw(name transdate employee); + my $sortorder = $form->sort_order(\@a, \%ordinal); + + my $glwhere = $where; + $glwhere =~ s/\(c.name\)/\(g.description\)/; + + # cycle through each id + foreach my $accno (split(/ /, $form->{paymentaccounts})) { + + $query = qq|SELECT id, accno, description + FROM chart + WHERE accno = '$accno'|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my $ref = $sth->fetchrow_hashref(NAME_lc); + push @{ $form->{PR} }, $ref; + $sth->finish; + + $query = qq|SELECT c.name, ac.transdate, sum(ac.amount) * $ml AS paid, + ac.source, ac.memo, e.name AS employee, a.till, a.curr + FROM acc_trans ac + JOIN $form->{db} a ON (ac.trans_id = a.id) + JOIN $table c ON (c.id = a.${table}_id) + LEFT JOIN employee e ON (a.employee_id = e.id) + $dpt_join + WHERE ac.chart_id = $ref->{id} + $where|; + + if ($form->{till} ne "") { + $query .= " AND a.invoice = '1' + AND NOT a.till IS NULL"; + + if ($myconfig->{role} eq 'user') { + $query .= " AND e.login = '$form->{login}'"; + } + } + + $query .= qq| + GROUP BY c.name, ac.transdate, ac.source, ac.memo, + e.name, a.till, a.curr + |; + + if ($form->{till} eq "") { +# don't need gl for a till + + $query .= qq| + UNION + SELECT g.description, ac.transdate, sum(ac.amount) * $ml AS paid, ac.source, + ac.memo, e.name AS employee, '' AS till, '' AS curr + FROM acc_trans ac + JOIN gl g ON (g.id = ac.trans_id) + LEFT JOIN employee e ON (g.employee_id = e.id) + $dpt_join + WHERE ac.chart_id = $ref->{id} + $glwhere + AND (ac.amount * $ml) > 0 + GROUP BY g.description, ac.transdate, ac.source, ac.memo, e.name + |; + + } + + $query .= qq| + ORDER BY $sortorder|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $pr = $sth->fetchrow_hashref(NAME_lc)) { + push @{ $form->{$ref->{id}} }, $pr; + } + $sth->finish; + + } + + $dbh->disconnect; + +} + + +1; + + diff --git a/LedgerSMB/Session.pm b/LedgerSMB/Session.pm new file mode 100755 index 00000000..eb3a1208 --- /dev/null +++ b/LedgerSMB/Session.pm @@ -0,0 +1,143 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has undergone whitespace cleanup. +# +#====================================================================== +# This package contains session related functions: +# +# check - checks validity of session based on the user's cookie and login +# +# create - creates a new session, writes cookie upon success +# +# destroy - destroys session +#==================================================================== +package Session; + +sub session_check { + + my ($cookie, $form, %myconfig) = @_; + my ($sessionid, $token) = split /:/, $cookie; + + # connect to database + my $dbh = DBI->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}); + + my $checkQuery = $dbh->prepare("SELECT sl_login FROM session WHERE session_id = ? AND token = ? AND last_used > now() - ?::interval"); + + my $updateAge = $dbh->prepare("UPDATE session SET last_used = now() WHERE session_id = ?;"); + + #must be an integer + $sessionid =~ s/[^0-9]//g; + $sessionid = int $sessionid; + + #must be 32 chars long and contain hex chars + $token =~ s/[^0-9a-f]//g; + $token = substr($token, 0, 32); + + if (!$myconfig{timeout}){ + $timeout = "1 day"; + } else { + $timeout = "$myconfig{timeout} seconds"; + } + + $checkQuery->execute($sessionid, $token, $timeout) || $form->dberror('Looking for session: '); + my $sessionValid = $checkQuery->rows; + + if($sessionValid){ + + #user has a valid session cookie, now check the user + my ($sessionLogin) = $checkQuery->fetchrow_array; + + my $login = $form->{login}; + $login =~ s/[^a-zA-Z0-9@.-]//g; + + if($sessionLogin eq $login){ + $updateAge->execute($sessionid) || $form->dberror('Updating session age: '); + return 1; + + } else { + #something's wrong, they have the cookie, but wrong user. Hijack attempt? + #delete the cookie in the browser + print qq|Set-Cookie: LedgerSMB=; path=/;\n|; + return 0; + } + + } else { + #cookie is not valid + #delete the cookie in the browser + print qq|Set-Cookie: LedgerSMB=; path=/;\n|; + print qq|Set-Cookie: DiedHere=true; path=/;\n|; + return 0; + } +} + +sub session_create { + my ($form, %myconfig) = @_; + + # connect to database + my $dbh = DBI->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}); + + # TODO Change this to use %myconfig + my $deleteExisting = $dbh->prepare("DELETE FROM session WHERE sl_login = ? AND age(last_used) > ?::interval"); + + my $seedRandom = $dbh->prepare("SELECT setseed(?);"); + + my $fetchSequence = $dbh->prepare("SELECT nextval('session_session_id_seq'), md5(random());"); + + my $createNew = $dbh->prepare("INSERT INTO session (session_id, sl_login, token) VALUES(?, ?, ?);"); + + + # this is assuming that $form->{login} is safe, which might be a bad assumption + # so, I'm going to remove some chars, which might make previously valid logins invalid + my $login = $form->{login}; + $login =~ s/[^a-zA-Z0-9@.-]//g; + + #delete any existing stale sessions with this login if they exist + if (!$myconfig{timeout}){ + $myconfig{timeout} = 86400; + } + + $deleteExisting->execute($login, "$myconfig{timeout} seconds") || $form->dberror('Delete from session: '); + + #doing the md5 and random stuff in the db so that LedgerSMB won't + #require new perl modules (Digest::MD5 and a good random generator) + $fetchSequence->execute() || $form->dberror('Fetch sequence id: '); + my ($newSessionID, $newToken) = $fetchSequence->fetchrow_array; + + #create a new session + $createNew->execute($newSessionID, $login, $newToken) || $form->dberror('Create new session: '); + + #reseed the random number generator + my $randomSeed = 1.0 * ('0.'. (time() ^ ($$ + ($$ <<15)))); + $seedRandom->execute($randomSeed)|| $form->dberror('Reseed random generator: ');; + + $newCookieValue = $newSessionID . ':' . $newToken; + + #now set the cookie in the browser + #TODO set domain from ENV, also set path to install path + print qq|Set-Cookie: LedgerSMB=$newCookieValue; path=/;\n|; + $form->{LedgerSMB} = $newCookieValue; +} + +sub session_destroy { + my ($form) = @_; + + my $login = $form->{login}; + $login =~ s/[^a-zA-Z0-9@.-]//g; + + # connect to database + my $dbh = DBI->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}); + + my $deleteExisting = $dbh->prepare("DELETE FROM session WHERE sl_login = ?;"); + $deleteExisting->execute($login) || $form->dberror('Delete from session: '); + + #delete the cookie in the browser + print qq|Set-Cookie: LedgerSMB=; path=/;\n|; + +} + +1; diff --git a/LedgerSMB/User.pm b/LedgerSMB/User.pm new file mode 100755 index 00000000..2398b06b --- /dev/null +++ b/LedgerSMB/User.pm @@ -0,0 +1,927 @@ +#===================================================================== +# LedgerSMB +# Small Medium Business Accounting software +# +# See COPYRIGHT file for copyright information +#====================================================================== +# +# This file has NOT undergone whitespace cleanup. +# +#====================================================================== +# +# user related functions +# +#===================================================================== + +package User; + + +sub new { + my ($type, $memfile, $login) = @_; + my $self = {}; + + if ($login ne "") { + &error("", "$memfile locked!") if (-f "${memfile}.LCK"); + + open(MEMBER, "$memfile") or &error("", "$memfile : $!"); + + while (<MEMBER>) { + if (/^\[$login\]/) { + while (<MEMBER>) { + last if /^\[/; + next if /^(#|\s)/; + + # remove comments + s/^\s*#.*//g; + + # remove any trailing whitespace + s/^\s*(.*?)\s*$/$1/; + + ($key, $value) = split /=/, $_, 2; + + $self->{$key} = $value; + } + + $self->{login} = $login; + + last; + } + } + close MEMBER; + } + + bless $self, $type; +} + + +sub country_codes { + + my %cc = (); + my @language = (); + + # scan the locale directory and read in the LANGUAGE files + opendir DIR, "locale"; + + my @dir = grep !/(^\.\.?$|\..*)/, readdir DIR; + + foreach my $dir (@dir) { + next unless open(FH, "locale/$dir/LANGUAGE"); + @language = <FH>; + close FH; + + $cc{$dir} = "@language"; + } + + closedir(DIR); + + %cc; + +} + + +sub login { + my ($self, $form, $userspath) = @_; + + my $rc = -1; + + if ($self->{login} ne "") { + + if ($self->{password} ne "") { + my $password = crypt $form->{password}, substr($self->{login}, 0, 2); + if ($self->{password} ne $password) { + return -1; + } + } + + unless (-f "$userspath/$self->{login}.conf") { + $self->create_config("$userspath/$self->{login}.conf"); + } + + do "$userspath/$self->{login}.conf"; + $myconfig{dbpasswd} = unpack 'u', $myconfig{dbpasswd}; + + # check if database is down + my $dbh = DBI->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}) or $self->error($DBI::errstr); + + # we got a connection, check the version + my $query = qq|SELECT version FROM defaults|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my ($dbversion) = $sth->fetchrow_array; + $sth->finish; + + # add login to employee table if it does not exist + # no error check for employee table, ignore if it does not exist + my $login = $self->{login}; + $login =~ s/@.*//; + $query = qq|SELECT id FROM employee WHERE login = '$login'|; + $sth = $dbh->prepare($query); + $sth->execute; + + my ($id) = $sth->fetchrow_array; + $sth->finish; + + if (! $id) { + my ($employeenumber) = $form->update_defaults(\%myconfig, "employeenumber", $dbh); + + $query = qq|INSERT INTO employee (login, employeenumber, name, workphone, + role) + VALUES ('$login', '$employeenumber', '$myconfig{name}', + '$myconfig{tel}', '$myconfig{role}')|; + $dbh->do($query); + } + $dbh->disconnect; + + $rc = 0; + + + if ($form->{dbversion} ne $dbversion) { + $rc = -3; + $dbupdate = (calc_version($dbversion) < calc_version($form->{dbversion})); + } + + if ($dbupdate) { + $rc = -4; + + # if DB2 bale out + if ($myconfig{dbdriver} eq 'DB2') { + $rc = -2; + } + } + } + + $rc; + +} + + +sub check_recurring { + my ($self, $form) = @_; + + $self->{dbpasswd} = unpack 'u', $self->{dbpasswd}; + + my $dbh = DBI->connect($self->{dbconnect}, $self->{dbuser}, $self->{dbpasswd}) or $form->dberror; + + my $query = qq|SELECT count(*) FROM recurring + WHERE enddate >= current_date AND nextdate <= current_date|; + ($_) = $dbh->selectrow_array($query); + + $dbh->disconnect; + + $_; + +} + + +sub dbconnect_vars { + my ($form, $db) = @_; + + my %dboptions = ( + 'Pg' => { + 'yy-mm-dd' => 'set DateStyle to \'ISO\'', + 'mm/dd/yy' => 'set DateStyle to \'SQL, US\'', + 'mm-dd-yy' => 'set DateStyle to \'POSTGRES, US\'', + 'dd/mm/yy' => 'set DateStyle to \'SQL, EUROPEAN\'', + 'dd-mm-yy' => 'set DateStyle to \'POSTGRES, EUROPEAN\'', + 'dd.mm.yy' => 'set DateStyle to \'GERMAN\'' + }, + 'Oracle' => { + 'yy-mm-dd' => 'ALTER SESSION SET NLS_DATE_FORMAT = \'YY-MM-DD\'', + 'mm/dd/yy' => 'ALTER SESSION SET NLS_DATE_FORMAT = \'MM/DD/YY\'', + 'mm-dd-yy' => 'ALTER SESSION SET NLS_DATE_FORMAT = \'MM-DD-YY\'', + 'dd/mm/yy' => 'ALTER SESSION SET NLS_DATE_FORMAT = \'DD/MM/YY\'', + 'dd-mm-yy' => 'ALTER SESSION SET NLS_DATE_FORMAT = \'DD-MM-YY\'', + 'dd.mm.yy' => 'ALTER SESSION SET NLS_DATE_FORMAT = \'DD.MM.YY\'', + } + ); + + + $form->{dboptions} = $dboptions{$form->{dbdriver}}{$form->{dateformat}}; + + if ($form->{dbdriver} =~ /Pg/) { + $form->{dbconnect} = "dbi:$form->{dbdriver}:dbname=$db"; + } + + if ($form->{dbdriver} eq 'Oracle') { + $form->{dbconnect} = "dbi:Oracle:sid=$form->{sid}"; + } + + if ($form->{dbhost}) { + $form->{dbconnect} .= ";host=$form->{dbhost}"; + } + if ($form->{dbport}) { + $form->{dbconnect} .= ";port=$form->{dbport}"; + } + +} + + +sub dbdrivers { + + my @drivers = DBI->available_drivers(); + +# return (grep { /(Pg|Oracle|DB2)/ } @drivers); + return (grep { /Pg$/ } @drivers); + +} + + +sub dbsources { + my ($self, $form) = @_; + + my @dbsources = (); + my ($sth, $query); + + $form->{dbdefault} = $form->{dbuser} unless $form->{dbdefault}; + $form->{sid} = $form->{dbdefault}; + &dbconnect_vars($form, $form->{dbdefault}); + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + + if ($form->{dbdriver} eq 'Pg') { + + $query = qq|SELECT datname FROM pg_database|; + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($db) = $sth->fetchrow_array) { + + if ($form->{only_acc_db}) { + + next if ($db =~ /^template/); + + &dbconnect_vars($form, $db); + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + $query = qq|SELECT tablename FROM pg_tables + WHERE tablename = 'defaults' + AND tableowner = '$form->{dbuser}'|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + if ($sth->fetchrow_array) { + push @dbsources, $db; + } + $sth->finish; + $dbh->disconnect; + next; + } + push @dbsources, $db; + } + } + + if ($form->{dbdriver} eq 'Oracle') { + if ($form->{only_acc_db}) { + $query = qq|SELECT owner FROM dba_objects + WHERE object_name = 'DEFAULTS' + AND object_type = 'TABLE'|; + } else { + $query = qq|SELECT username FROM dba_users|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($db) = $sth->fetchrow_array) { + push @dbsources, $db; + } + } + + +# JJR + if ($form->{dbdriver} eq 'DB2') { + if ($form->{only_acc_db}) { + $query = qq|SELECT tabschema FROM syscat.tables WHERE tabname = 'DEFAULTS'|; + } else { + $query = qq|SELECT DISTINCT schemaname FROM syscat.schemata WHERE definer != 'SYSIBM' AND schemaname != 'NULLID'|; + } + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($db) = $sth->fetchrow_array) { + push @dbsources, $db; + } + } +# End JJR + +# the above is not used but leave it in for future reference +# DS, Oct. 28, 2003 + + + $sth->finish; + $dbh->disconnect; + + return @dbsources; + +} + + +sub dbcreate { + my ($self, $form) = @_; + + my %dbcreate = ( 'Pg' => qq|CREATE DATABASE "$form->{db}"|, + 'Oracle' => qq|CREATE USER "$form->{db}" DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP IDENTIFIED BY "$form->{db}"|); + + $dbcreate{Pg} .= " WITH ENCODING = '$form->{encoding}'" if $form->{encoding}; + + $form->{sid} = $form->{dbdefault}; + &dbconnect_vars($form, $form->{dbdefault}); + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + my $query = qq|$dbcreate{$form->{dbdriver}}|; + $dbh->do($query); + + if ($form->{dbdriver} eq 'Oracle') { + $query = qq|GRANT CONNECT,RESOURCE TO "$form->{db}"|; + $dbh->do($query) || $form->dberror($query); + } + $dbh->disconnect; + + + # setup variables for the new database + if ($form->{dbdriver} eq 'Oracle') { + $form->{dbuser} = $form->{db}; + $form->{dbpasswd} = $form->{db}; + } + + + &dbconnect_vars($form, $form->{db}); + + $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + # create the tables + my $dbdriver = ($form->{dbdriver} =~ /Pg/) ? 'Pg' : $form->{dbdriver}; + + my $filename = qq|sql/${dbdriver}-tables.sql|; + $self->process_query($form, $dbh, $filename); + + # create functions + $filename = qq|sql/${dbdriver}-functions.sql|; + $self->process_query($form, $dbh, $filename); + + # load gifi + ($filename) = split /_/, $form->{chart}; + $filename =~ s/_//; + $self->process_query($form, $dbh, "sql/${filename}-gifi.sql"); + + # load chart of accounts + $filename = qq|sql/$form->{chart}-chart.sql|; + $self->process_query($form, $dbh, $filename); + + # create indices + $filename = qq|sql/${dbdriver}-indices.sql|; + $self->process_query($form, $dbh, $filename); + + # create custom tables and functions + my $item; + foreach $item (qw(tables functions)) { + $filename = "sql/${dbdriver}-custom_${item}.sql"; + if (-f "$filename") { + $self->process_query($form, $dbh, $filename); + } + } + + $dbh->disconnect; + +} + + + +sub process_query { + my ($self, $form, $dbh, $filename) = @_; + + return unless (-f $filename); + + open(FH, "$filename") or $form->error("$filename : $!\n"); + my $query = ""; + my $loop = 0; + my $sth; + + + while (<FH>) { + + if ($loop && /^--\s*end\s*(procedure|function|trigger)/i) { + $loop = 0; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + $sth->finish; + + $query = ""; + next; + } + + if ($loop || /^create *(or replace)? *(procedure|function|trigger)/i) { + $loop = 1; + next if /^(--.*|\s+)$/; + + $query .= $_; + next; + } + + # don't add comments or empty lines + next if /^(--.*|\s+)$/; + + # anything else, add to query + $query .= $_; + + if (/;\s*$/) { + # strip ;... Oracle doesn't like it + $query =~ s/;\s*$//; + $query =~ s/\\'/''/g; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + $sth->finish; + + $query = ""; + } + + } + close FH; + +} + + + +sub dbdelete { + my ($self, $form) = @_; + + my %dbdelete = ( 'Pg' => qq|DROP DATABASE "$form->{db}"|, + 'Oracle' => qq|DROP USER $form->{db} CASCADE| + ); + + $form->{sid} = $form->{dbdefault}; + &dbconnect_vars($form, $form->{dbdefault}); + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + my $query = qq|$dbdelete{$form->{dbdriver}}|; + $dbh->do($query) || $form->dberror($query); + + $dbh->disconnect; + +} + + + +sub dbsources_unused { + my ($self, $form, $memfile) = @_; + + my @dbexcl = (); + my @dbsources = (); + + $form->error("$memfile locked!") if (-f "${memfile}.LCK"); + + # open members file + open(FH, "$memfile") or $form->error("$memfile : $!"); + + while (<FH>) { + if (/^dbname=/) { + my ($null,$item) = split /=/; + push @dbexcl, $item; + } + } + + close FH; + + $form->{only_acc_db} = 1; + my @db = &dbsources("", $form); + + push @dbexcl, $form->{dbdefault}; + + foreach $item (@db) { + unless (grep /$item$/, @dbexcl) { + push @dbsources, $item; + } + } + + return @dbsources; + +} + + +sub dbneedsupdate { + my ($self, $form) = @_; + + my %dbsources = (); + my $query; + + $form->{sid} = $form->{dbdefault}; + &dbconnect_vars($form, $form->{dbdefault}); + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + if ($form->{dbdriver} =~ /Pg/) { + + $query = qq|SELECT d.datname FROM pg_database d, pg_user u + WHERE d.datdba = u.usesysid + AND u.usename = '$form->{dbuser}'|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($db) = $sth->fetchrow_array) { + + next if ($db =~ /^template/); + + &dbconnect_vars($form, $db); + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + $query = qq|SELECT tablename FROM pg_tables + WHERE tablename = 'defaults'|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + if ($sth->fetchrow_array) { + $query = qq|SELECT version FROM defaults|; + my $sth = $dbh->prepare($query); + $sth->execute; + + if (my ($version) = $sth->fetchrow_array) { + $dbsources{$db} = $version; + } + $sth->finish; + } + $sth->finish; + $dbh->disconnect; + } + $sth->finish; + } + + + if ($form->{dbdriver} eq 'Oracle') { + $query = qq|SELECT owner FROM dba_objects + WHERE object_name = 'DEFAULTS' + AND object_type = 'TABLE'|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($db) = $sth->fetchrow_array) { + + $form->{dbuser} = $db; + &dbconnect_vars($form, $db); + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + $query = qq|SELECT version FROM defaults|; + my $sth = $dbh->prepare($query); + $sth->execute; + + if (my ($version) = $sth->fetchrow_array) { + $dbsources{$db} = $version; + } + $sth->finish; + $dbh->disconnect; + } + $sth->finish; + } + + +# JJR + if ($form->{dbdriver} eq 'DB2') { + $query = qq|SELECT tabschema FROM syscat.tables WHERE tabname = 'DEFAULTS'|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my ($db) = $sth->fetchrow_array) { + + &dbconnect_vars($form, $db); + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + $query = qq|SELECT version FROM defaults|; + my $sth = $dbh->prepare($query); + $sth->execute; + + if (my ($version) = $sth->fetchrow_array) { + $dbsources{$db} = $version; + } + $sth->finish; + $dbh->disconnect; + } + $sth->finish; + } +# End JJR + +# code for DB2 is not used, keep for future reference +# DS, Oct. 28, 2003 + + $dbh->disconnect; + + %dbsources; + +} + + +sub dbupdate { + my ($self, $form) = @_; + + $form->{sid} = $form->{dbdefault}; + + my @upgradescripts = (); + my $query; + my $rc = -2; + + if ($form->{dbupdate}) { + # read update scripts into memory + opendir SQLDIR, "sql/." or $form->error($!); + @upgradescripts = sort script_version grep /$form->{dbdriver}-upgrade-.*?\.sql$/, readdir SQLDIR; + closedir SQLDIR; + } + + + foreach my $db (split / /, $form->{dbupdate}) { + + next unless $form->{$db}; + + # strip db from dataset + $db =~ s/^db//; + &dbconnect_vars($form, $db); + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + + # check version + $query = qq|SELECT version FROM defaults|; + my $sth = $dbh->prepare($query); + # no error check, let it fall through + $sth->execute; + + my $version = $sth->fetchrow_array; + $sth->finish; + + next unless $version; + + $version = calc_version($version); + my $dbversion = calc_version($form->{dbversion}); + + foreach my $upgradescript (@upgradescripts) { + my $a = $upgradescript; + $a =~ s/(^$form->{dbdriver}-upgrade-|\.sql$)//g; + + my ($mindb, $maxdb) = split /-/, $a; + $mindb = calc_version($mindb); + $maxdb = calc_version($maxdb); + + next if ($version >= $maxdb); + + # exit if there is no upgrade script or version == mindb + last if ($version < $mindb || $version >= $dbversion); + + # apply upgrade + $self->process_query($form, $dbh, "sql/$upgradescript"); + + $version = $maxdb; + + } + + $rc = 0; + $dbh->disconnect; + + } + + $rc; + +} + + +sub calc_version { + + my @v = split /\./, $_[0]; + my $version = 0; + my $i; + + for ($i = 0; $i <= $#v; $i++) { + $version *= 1000; + $version += $v[$i]; + } + + return $version; + +} + + +sub script_version { + my ($my_a, $my_b) = ($a, $b); + + my ($a_from, $a_to, $b_from, $b_to); + my ($res_a, $res_b, $i); + + $my_a =~ s/.*-upgrade-//; + $my_a =~ s/.sql$//; + $my_b =~ s/.*-upgrade-//; + $my_b =~ s/.sql$//; + ($a_from, $a_to) = split(/-/, $my_a); + ($b_from, $b_to) = split(/-/, $my_b); + + $res_a = calc_version($a_from); + $res_b = calc_version($b_from); + + if ($res_a == $res_b) { + $res_a = calc_version($a_to); + $res_b = calc_version($b_to); + } + + return $res_a <=> $res_b; + +} + + +sub create_config { + my ($self, $filename) = @_; + + + @config = &config_vars; + + open(CONF, ">$filename") or $self->error("$filename : $!"); + + # create the config file + print CONF qq|# configuration file for $self->{login} + +\%myconfig = ( +|; + + foreach $key (sort @config) { + $self->{$key} =~ s/\\/\\\\/g; + $self->{$key} =~ s/'/\\'/g; + print CONF qq| $key => '$self->{$key}',\n|; + } + + + print CONF qq|);\n\n|; + + close CONF; + +} + + +sub save_member { + my ($self, $memberfile, $userspath) = @_; + + # format dbconnect and dboptions string + &dbconnect_vars($self, $self->{dbname}); + + $self->error("$memberfile locked!") if (-f "${memberfile}.LCK"); + open(FH, ">${memberfile}.LCK") or $self->error("${memberfile}.LCK : $!"); + close(FH); + + if (! open(CONF, "+<$memberfile")) { + unlink "${memberfile}.LCK"; + $self->error("$memberfile : $!"); + } + + @config = <CONF>; + + seek(CONF, 0, 0); + truncate(CONF, 0); + + while ($line = shift @config) { + last if ($line =~ /^\[$self->{login}\]/); + print CONF $line; + } + + # remove everything up to next login or EOF + while ($line = shift @config) { + last if ($line =~ /^\[/); + } + + # this one is either the next login or EOF + print CONF $line; + + while ($line = shift @config) { + print CONF $line; + } + + print CONF qq|[$self->{login}]\n|; + + if ($self->{packpw}) { + $self->{dbpasswd} = pack 'u', $self->{dbpasswd}; + chop $self->{dbpasswd}; + } + + if ($self->{password} ne $self->{old_password}) { + $self->{password} = crypt $self->{password}, substr($self->{login}, 0, 2) if $self->{password}; + } + + if ($self->{'root login'}) { + @config = qw(password); + } else { + @config = &config_vars; + } + + # replace \r\n with \n + for (qw(address signature)) { $self->{$_} =~ s/\r?\n/\\n/g } + + for (sort @config) { print CONF qq|$_=$self->{$_}\n| } + + print CONF "\n"; + close CONF; + unlink "${memberfile}.LCK"; + + # create conf file + if (! $self->{'root login'}) { + $self->create_config("$userspath/$self->{login}.conf"); + + $self->{dbpasswd} =~ s/\\'/'/g; + $self->{dbpasswd} =~ s/\\\\/\\/g; + $self->{dbpasswd} = unpack 'u', $self->{dbpasswd}; + + # check if login is in database + my $dbh = DBI->connect($self->{dbconnect}, $self->{dbuser}, $self->{dbpasswd}, {AutoCommit => 0}) or $self->error($DBI::errstr); + + # add login to employee table if it does not exist + my $login = $self->{login}; + $login =~ s/@.*//; + my $query = qq|SELECT id FROM employee WHERE login = '$login'|; + my $sth = $dbh->prepare($query); + $sth->execute; + + my ($id) = $sth->fetchrow_array; + $sth->finish; + + if ($id) { + $query = qq|UPDATE employee SET + role = '$self->{role}', + email = '$self->{email}', + name = '$self->{name}' + WHERE login = '$login'|; + + } else { + my ($employeenumber) = Form::update_defaults("", \%$self, "employeenumber", $dbh); + $query = qq|INSERT INTO employee (login, employeenumber, name, workphone, + role, email, sales) + VALUES ('$login', '$employeenumber', '$self->{name}', + '$self->{tel}', '$self->{role}', '$self->{email}', '1')|; + } + + $dbh->do($query); + $dbh->commit; + $dbh->disconnect; + + } + +} + + +sub delete_login { + my ($self, $form) = @_; + + my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, {AutoCommit} => 0) or $form->dberror; + + my $login = $form->{login}; + $login =~ s/@.*//; + my $query = qq|SELECT id FROM employee + WHERE login = '$login'|; + my $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + my ($id) = $sth->fetchrow_array; + $sth->finish; + + my $query = qq|UPDATE employee SET + login = NULL, + enddate = current_date + WHERE login = '$login'|; + $dbh->do($query); + + $dbh->commit; + $dbh->disconnect; + +} + + +sub config_vars { + + my @conf = qw(acs address businessnumber company countrycode + currency dateformat dbconnect dbdriver dbhost dbname dboptions + dbpasswd dbport dbuser email fax menuwidth name numberformat + password printer role sid signature stylesheet tel + templates timeout vclimit); + + @conf; + +} + + +sub error { + my ($self, $msg) = @_; + + if ($ENV{HTTP_USER_AGENT}) { + print qq|Content-Type: text/html + +<body bgcolor=ffffff> + +<h2><font color=red>Error!</font></h2> +<p><b>$msg</b>|; + + } + + die "Error: $msg\n"; + +} + + +1; + |