diff --git a/LedgerSMB.pm b/LedgerSMB.pm index 30c3b2b826..9a38149db6 100755 --- a/LedgerSMB.pm +++ b/LedgerSMB.pm @@ -276,7 +276,7 @@ sub escape { my $regex = qr/([^a-zA-Z0-9_.-])/; $str =~ s/$regex/sprintf("%%%02x", ord($1))/ge; - $str; + return $str; } sub is_blank { @@ -471,7 +471,7 @@ sub format_amount { sub parse_amount { my $self = shift @_; my %args = @_; - my $myconfig = $args{user}; + my $myconfig = $args{user} || $self->{_user}; my $amount = $args{amount}; if ( $amount eq '' or ! defined $amount) { @@ -809,6 +809,18 @@ sub remove_cgi_globals { } } } + +sub take_top_level { + my ($self) = @_; + my $return_hash = {}; + for my $key (keys %$self){ + if (!ref($self->{$key}) && $key !~ /^\./){ + $return_hash->{$key} = $self->{$key} + } + } + return $return_hash; +} + 1; diff --git a/LedgerSMB/Batch.pm b/LedgerSMB/Batch.pm index 67bc0ad908..a09031cd56 100644 --- a/LedgerSMB/Batch.pm +++ b/LedgerSMB/Batch.pm @@ -31,13 +31,21 @@ sub get_search_results { sub post { my ($self) = @_; ($self->{post_return_ref}) = $self->exec_method(funcname => 'batch_post'); + $self->{dbh}->commit; return $self->{post_return_ref}; } sub delete { my ($self) = @_; ($self->{delete_ref}) = $self->exec_method(funcname => 'batch_delete'); + $self->{dbh}->commit; return $self->{delete_ref}; } +sub list_vouchers { + my ($self) = @_; + @{$self->{vouchers}} = $self->exec_method(funcname => 'voucher_list'); + return @{$self->{vouchers}}; +} + 1; diff --git a/LedgerSMB/DBObject/Company.pm b/LedgerSMB/DBObject/Company.pm index 13b9ff6528..0e56567b7c 100644 --- a/LedgerSMB/DBObject/Company.pm +++ b/LedgerSMB/DBObject/Company.pm @@ -14,8 +14,10 @@ sub set_entity_class { sub save { my $self = shift @_; $self->set_entity_class(); + $self->{threshold} = $self->parse_amount(amount => $self->{threshold}); my ($ref) = $self->exec_method(funcname => 'entity_credit_save'); $self->{entity_id} = $ref->{entity_credit_save}; + $self->{threshold} = $self->format_amount(amount => $self->{threshold}); $self->{dbh}->commit; } @@ -88,6 +90,7 @@ sub get { $self->set_entity_class(); my ($ref) = $self->exec_method(funcname => 'entity__retrieve_credit'); $self->merge($ref); + $self->{threshold} = $self->format_amount(amount => $self->{threshold}); $self->{name} = $self->{legal_name}; diff --git a/README.sql-ledger b/README.sql-ledger index c29d8410f2..1c68f0ca64 100644 --- a/README.sql-ledger +++ b/README.sql-ledger @@ -12,4 +12,5 @@ a working LedgerSMB installation. The database will be updated on first login. -You will also want to migrate your configuration by running the SL2LS.pl script included in this directory. +You will also want to migrate your configuration by running the SL2LS.pl script +included in this directory. diff --git a/TODO b/TODO index f6c2e01734..f5253e85fa 100644 --- a/TODO +++ b/TODO @@ -3,12 +3,6 @@ For 1.3: 1) Refactor admin.pl. Merge user management with main UI and create separate dataset management interface. (in progress, Chris) -2) Add real security enforcement (in progress, Chris, part of admin reworking) +2) Setup wizard. -3) Refactor contact management code - -4) Setup wizard. - -Additional prerequisites include: -1) Refactoring HR code. (In progress, Chris) diff --git a/UI/Contact/contact.html b/UI/Contact/contact.html index 3a8d27b334..311660b83f 100644 --- a/UI/Contact/contact.html +++ b/UI/Contact/contact.html @@ -151,6 +151,11 @@ name = "entity_id" value = entity_id } ?> + + diff --git a/UI/batch/filter.html b/UI/batch/filter.html index 3cb690894c..87c1d84724 100644 --- a/UI/batch/filter.html +++ b/UI/batch/filter.html @@ -30,7 +30,7 @@ options = batch_users value_attr = "entity_id" text_attr = "username" - name = "created_by" + name = "created_by_eid" default_values = [created_by] } ?>
diff --git a/UI/lib/elements.html b/UI/lib/elements.html index 4345e7ee3d..e22bd02670 100644 --- a/UI/lib/elements.html +++ b/UI/lib/elements.html @@ -156,7 +156,7 @@ -
+

+ diff --git a/UI/payments/payments_detail.html b/UI/payments/payments_detail.html index bd41fcd22d..1e75c603f2 100644 --- a/UI/payments/payments_detail.html +++ b/UI/payments/payments_detail.html @@ -13,6 +13,11 @@ +
@@ -177,7 +182,11 @@ } ?> - +     -   -   +   + +   +   @@ -249,7 +260,7 @@ diff --git a/scripts/vouchers.pl b/scripts/vouchers.pl index f847455608..4a3c1129b2 100644 --- a/scripts/vouchers.pl +++ b/scripts/vouchers.pl @@ -29,6 +29,15 @@ sub create_batch { } sub create_vouchers { + my ($request) = shift @_; + my $batch = LedgerSMB::Batch->new({base => $request}); + $batch->{batch_class} = $request->{batch_type}; + $batch->create; + add_vouchers($batch); +} + + +sub add_vouchers { # This function is not safe for caching as long as the scripts are in bin. # This is because these scripts import all functions into the *current* # namespace. People using fastcgi and modperl should *not* cache this @@ -36,11 +45,7 @@ sub create_vouchers { # Also-- request is in 'our' scope here due to the redirect logic. our ($request) = shift @_; use LedgerSMB::Form; - my $batch = LedgerSMB::Batch->new({base => $request}); - $batch->{batch_class} = $request->{batch_type}; - $batch->create; - our $vouchers_dispatch = { payable => {script => 'bin/ap.pl', function => sub {add()}}, @@ -61,8 +66,6 @@ sub create_vouchers { }; - # Note that the line below is generally considered incredibly bad form. - # However, the code we are including is going to require it for now. -- CT our $form = new Form; our $locale = $request->{_locale}; @@ -84,10 +87,13 @@ sub create_vouchers { $form->{script} =~ s|.*/||; if ($script =~ /^bin/){ + # Note that the line below is generally considered incredibly bad form. + # However, the code we are including is going to require it for now. + # -- CT { no strict; no warnings 'redefine'; do $script; } } elsif ($script =~ /scripts/) { - + # Maybe we should move this to a require statement? --CT { do $script } } @@ -95,19 +101,242 @@ sub create_vouchers { $vouchers_dispatch->{$request->{batch_type}}{function}($request); } +sub search_batch { + my ($request) = @_; + my $batch_request = LedgerSMB::Batch->new(base => $request); + $batch_request->get_search_criteria(); + my $template = LedgerSMB::Template->new( + user => $request->{_user}, + locale => $request->{_locale}, + path => 'UI/batch', + template => 'filter', + format => 'HTML', + ); + $template->render($batch_request); +} + +sub list_batches { + my ($request) = @_; + my $batch = LedgerSMB::Batch->new(base => $request); + my @search_results = $batch->get_search_results; + $batch->{script} = "vouchers.pl"; + + my @columns = + qw(select id control_code description transaction_total payment_total); + + my $base_href = "vouchers.pl"; + my $search_href = "$base_href?action=list_batches"; + my $batch_href = "$base_href?action=get_batch"; + + for my $key ( + qw(class_id approved created_by description amount_gt amount_lt) + ){ + $search_href .= "&$key=$batch->{key}"; + } + + my %column_heading = ( + 'select' => $batch->{_locale}->text('Select'), + transaction_total => { + text => $batch->{_locale}->text('AR/AP/GL Total'), + href => "$search_href&order_by=transaction_total" + }, + payment_total => { + text => $batch->{_locale}->text('Paid/Received Total'), + href => "$search_href&order_by=payment_total" + }, + description => { + text => $batch->{_locale}->text('Description'), + href => "$search_href&order_by=description" + }, + control_code => { + text => $batch->{_locale}->text('Batch Number'), + href => "$search_href&order_by=control_code" + }, + id => { + text => $batch->{_locale}->text('ID'), + href => "$search_href&order_by=control_code" + }, + ); + my $count = 0; + my @rows; + for my $result (@search_results){ + ++$count; + $batch->{"row_$count"} = $result->{id}; + push @rows, { + 'select' => { + input => { + type => 'checkbox', + value => 1, + name => "batch_$result->{id}" + } + }, + transaction_total => $batch->format_amount( + amount => $result->{transaction_total} + ), + payment_total => $batch->format_amount ( + amount => $result->{payment_total} + ), + description => $result->{description}, + control_code => { + text => $result->{control_code}, + href => "$batch_href&batch_id=$result->{id}", + + }, + id => $result->{id}, + }; + } + $batch->{rowcount} = $count; + my $template = LedgerSMB::Template->new( + user => $request->{_user}, + locale => $request->{_locale}, + path => 'UI', + template => 'form-dynatable', + format => ($batch->{format}) ? $batch->{format} : 'HTML', + ); + + my $hiddens = $batch->take_top_level(); + $batch->{rowcount} = "$count"; + delete $batch->{search_results}; + + $template->render({ + form => $batch, + columns => \@columns, + heading => \%column_heading, + rows => \@rows, + hiddens => $hiddens, + buttons => [{ + name => 'action', + type => 'submit', + text => $request->{_locale}->text('Post'), + value => 'batch_approve', + class => 'submit', + },{ + name => 'action', + type => 'submit', + text => $request->{_locale}->text('Delete'), + value => 'batch_delete', + class => 'submit', + }] + }); + +} + sub get_batch { + my ($request) = @_; + my $batch = LedgerSMB::Batch->new(base => $request); + my $rows = []; + + $batch->{id} ||= $batch->{batch_id}; + # $batch->get; + my @vouchers = $batch->list_vouchers; + + my $base_href = "vouchers.pl?action=get_batch&batch_id=$batch->{batch_id}"; + + my @columns = qw(id description batch_class reference amount date); + my $heading = { + id => { + text => $request->{_locale}->text('ID'), + href => "$base_href&order_by=id" + }, + description => { + href => "$base_href&order_by=description", + text => $request->{_locale}->text('Description'), + }, + batch_class => { + text => $request->{_locale}->text('Class'), + href => "$base_href&order_by=class" + }, + amount => { + text => $request->{_locale}->text('Amount'), + href => "$base_href&order_by=amount" + }, + reference => { + text => $request->{_locale}->text('Source/Reference'), + href => "$base_href&order_by=reference" + }, + date => { + text => $request->{_locale}->text('Date'), + href => "$base_href&order_by=date" + } + }; + + my $classcount; + + for my $row (@vouchers) { + $classcount = ($classcount + 1) % 2; + $classcount ||= 0; + push @$rows, { + description => $row->{description}, + id => $row->{id}, + batch_class => $row->{batch_class}, + amount => $batch->format_amount(amount => $row->{amount}), + date => $row->{transaction_date}, + reference => $row->{reference}, + class => "listrow$classcount" + + }; + } + $batch->{title} = "Batch ID: $batch->{batch_id}"; + my $template = LedgerSMB::Template->new( + user => $request->{_user}, + locale => $request->{_locale}, + path => 'UI', + template => 'form-dynatable', + format => ($batch->{format}) ? $batch->{format} : 'HTML', + ); + my $hiddens = $batch->take_top_level(); + $template->render({ + form => $batch, + columns => \@columns, + heading => $heading, + rows => $rows, + hiddens => $hiddens, + buttons => [{ + name => 'action', + type => 'submit', + text => $request->{_locale}->text('Post'), + value => 'batch_approve', + class => 'submit', + },{ + name => 'action', + type => 'submit', + text => $request->{_locale}->text('Delete'), + value => 'batch_delete', + class => 'submit', + }] + }); + + } -sub list_vouchers { +sub list_batches_batch_delete { + batch_delete(@_); } -sub add_vouchers { +sub list_batches_batch_approve { + batch_approve(@_); } -sub approve_batch { +sub batch_approve { + my ($request) = @_; + my $batch = LedgerSMB::Batch->new(base => $request); + for my $count (1 .. $batch->{rowcount}){ + next unless $batch->{"batch_" . $batch->{"row_$count"}}; + $batch->{batch_id} = $batch->{"row_$count"}; + $batch->post; + } + search_batch($request); } -sub delete_batch { +sub batch_delete { + my ($request) = @_; + my $batch = LedgerSMB::Batch->new(base => $request); + for my $count (1 .. $batch->{rowcount}){ + next unless $batch->{"batch_" . $batch->{"row_$count"}}; + $batch->{batch_id} = $batch->{"row_$count"}; + $batch->delete; + } + search_batch($request); } eval { do "scripts/custom/Voucher.pl"}; diff --git a/sql/modules/Company.sql b/sql/modules/Company.sql index cac1a5accd..ff2795afa0 100644 --- a/sql/modules/Company.sql +++ b/sql/modules/Company.sql @@ -126,7 +126,10 @@ CREATE TYPE entity_credit_search_return AS ( pricegroup_id int, curr char(3), startdate date, - enddate date + enddate date, + ar_ap_account_id int, + cash_account_id int, + threshold numeric ); COMMENT ON TYPE entity_credit_search_return IS @@ -142,7 +145,8 @@ BEGIN SELECT c.legal_name, c.id, e.id, ec.entity_class, ec.discount, ec.taxincluded, ec.creditlimit, ec.terms, ec.meta_number, ec.business_id, ec.language_code, ec.pricegroup_id, - ec.curr::char(3), ec.startdate, ec.enddate + ec.curr::char(3), ec.startdate, ec.enddate, ec.ar_ap_account_id, + ec.cash_account_id, ec.threshold INTO out_row FROM company c JOIN entity e ON (c.entity_id = e.id) @@ -166,7 +170,9 @@ CREATE OR REPLACE FUNCTION entity_credit_save ( in_curr char, in_startdate date, in_enddate date, in_notes text, in_name text, in_tax_id TEXT, - in_threshold NUMERIC + in_threshold NUMERIC, + in_ar_ap_account_id int, + in_cash_account_id int ) returns INT as $$ @@ -178,7 +184,7 @@ CREATE OR REPLACE FUNCTION entity_credit_save ( BEGIN -- TODO: Move every table to an upsert mode independantly. - SELECT INTO v_row * FROM company WHERE id = in_id; + SELECT INTO v_row * FROM company WHERE legal_name = in_name; IF NOT FOUND THEN -- do some inserts @@ -209,7 +215,9 @@ CREATE OR REPLACE FUNCTION entity_credit_save ( startdate, enddate, discount_terms, - threshold + threshold, + ar_ap_account_id, + cash_account_id ) VALUES ( new_entity_id, @@ -226,7 +234,9 @@ CREATE OR REPLACE FUNCTION entity_credit_save ( in_startdate, in_enddate, in_discount_terms, - in_threshold + in_threshold, + in_ar_ap_account_id, + in_cash_account_id ); -- entity note class insert into entity_note (note_class, note, ref_key, vector) VALUES ( @@ -236,12 +246,14 @@ CREATE OR REPLACE FUNCTION entity_credit_save ( ELSIF FOUND THEN - update company set tax_id = in_tax_id where id = in_id; + update company set tax_id = in_tax_id where id = v_row.id; update entity_credit_account SET discount = in_discount, taxincluded = in_taxincluded, creditlimit = in_creditlimit, terms = in_terms, + ar_ap_account_id = in_ar_ap_account_id, + cash_account_id = in_cash_account_id, meta_number = in_meta_number, business_id = in_business_id, language_code = in_language, @@ -253,12 +265,7 @@ CREATE OR REPLACE FUNCTION entity_credit_save ( discount_terms = in_discount_terms where entity_id = v_row.entity_id; - - UPDATE entity_note SET - note = in_note - WHERE ref_key = v_row.entity_id; - return in_id; - + return v_row.entity_id; END IF; END; diff --git a/utils/process_queue/process_queue.pl b/utils/process_queue/process_queue.pl index 28401eb657..5bcc24cadd 100644 --- a/utils/process_queue/process_queue.pl +++ b/utils/process_queue/process_queue.pl @@ -28,19 +28,20 @@ sub on_notify { my $job_id = 1; while ($job_id){ ($job_id) = $dbh->selectrow_array( - "SELECT min(id) from pending_job + "SELECT id from pending_job WHERE completed_at IS NULL + ORDER BY id LIMIT 1 FOR UPDATE" ); if ($job_id){ $job_id = $dbh->quote($job_id); my ($job_class) = $dbh->selectrow_array( "select class from batch_class where id = - (select batch_class from pending_job where id = $job_id" + (select batch_class from pending_job where id = $job_id)" ); # Right now, we assume that every pending job has a batch id. # Longer-run we may need to use a template handle as well. -CT - $dbh->execute('SELECT ' . + $dbh->do('SELECT ' . $dbh->quote_identifier("job__process_$job_class") . "($job_id)" ); my $errstr = $dbh->errstr; @@ -60,6 +61,7 @@ sub on_notify { # The line below is necessary because the job process functions # use set session authorization so one must reconnect to reset # administrative permissions. -CT + $dbh->disconnect; $dbh = db_init(); } }