=pod =head1 NAME LedgerSMB::Scripts::payment - LedgerSMB class defining the Controller functions for payment handling. =head1 SYNOPSIS Defines the controller functions and workflow logic for payment processing. =head1 COPYRIGHT Copyright (c) 2007, David Mora R and Christian Ceballos B. Licensed to the public under the terms of the GNU GPL version 2 or later. Original copyright notice below. #===================================================================== # PLAXIS # Copyright (c) 2007 # # Author: David Mora R # Christian Ceballos B # # # # # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. =head1 METHODS =cut package LedgerSMB::Scripts::payment; use LedgerSMB::Template; use LedgerSMB::DBObject::Payment; use LedgerSMB::DBObject::Date; use strict; # CT: A few notes for future refactoring of this code: # 1: I don't think it is a good idea to make the UI too dependant on internal # code structures but I don't see a good alternative at the moment. # 2: CamelCasing: -1 =pod =item payment This method is used to set the filter screen and prints it, using the TT2 system. =back =cut sub payments { my ($request) = @_; my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); $payment->get_metadata(); my $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'payments_filter', format => 'HTML', ); $template->render($payment); } sub get_search_criteria { my ($request) = @_; my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); $payment->get_metadata(); if ($payment->{batch_id} && $payment->{batch_date}){ $payment->{date_reversed} = $payment->{batch_date}; } my $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'search', format => 'HTML', ); $template->render($payment); } sub get_search_results { my ($request) = @_; my $rows = []; my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); my @search_results = $payment->search; my $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI', template => 'form-dynatable', format => ($payment->{format}) ? $payment->{format} : 'HTML', ); my $base_url = "payment.pl?"; my $search_url = "$base_url"; for my $key (keys %{$request->take_top_level}){ if ($base_url =~ /\?$/){ $base_url .= "$key=$request->{key}"; } else { $base_url .= "&$key=$request->{key}"; } } my @columns = qw(selected meta_number date_paid amount source company_paid); my $contact_type = ($payment->{account_class} == 1) ? 'Vendor' : 'Customer'; # CT: Locale strings for gettext: # $request->{_locale}->text("Vendor Number"); # $request->{_locale}->text("Customer Number"); my $heading = { selected => $request->{_locale}->text('Selected'), company_paid => { text => $request->{_locale}->text('Company Name'), href => "$search_url&orderby=company_paid", }, meta_number => { text => $request->{_locale}->text( "$contact_type Number" ), href => "$search_url&orderby=meta_number", }, date_paid => { text => $request->{_locale}->text('Date Paid'), href => "$search_url&orderby=date_paid", }, amount => { text => $request->{_locale}->text('Total Paid'), href => "$search_url&orderby=amount", }, source => { text => $request->{_locale}->text('Source'), href => "$search_url&orderby=source", }, }; my $classcount; $classcount = 0; my $rowcount; $rowcount = 1; for my $line (@search_results){ $classcount ||= 0; $rowcount += 1; push(@$rows, { company_paid => $line->{company_paid}, amount => $request->format_amount(amount => $line->{amount}), i => "$classcount", date_paid => $line->{date_paid}, source => $line->{source}, meta_number => $line->{meta_number}, selected => { input => { type => "checkbox", name => "payment_$rowcount", value => "1", }, } }); $payment->{"credit_id_$rowcount"} = $line->{credit_id}; $payment->{"date_paid_$rowcount"} = $line->{date_paid}; $payment->{"source_$rowcount"} = $line->{source}; $classcount = ($classcount + 1) % 2; ++$rowcount; } $payment->{rowcount} = $rowcount; $payment->{script} = 'payment.pl'; $payment->{title} = $request->{_locale}->text("Payment Results"); my $hiddens = $payment->take_top_level; $template->render({ form => $payment, columns => \@columns, heading => $heading, hiddens => $payment->take_top_level, rows => $rows, buttons => [{ value => 'reverse_payments', name => 'action', class => 'submit', type => 'submit', text => $request->{_locale}->text('Reverse Payments'), }] }); } sub get_search_results_reverse_payments { my ($request) = @_; my $payment = LedgerSMB::DBObject::Payment->new({base => $request}); for my $count (1 .. $payment->{rowcount}){ if ($payment->{"payment_$count"}){ $payment->{credit_id} = $payment->{"credit_id_$count"}; $payment->{date_paid} = $payment->{"date_paid_$count"}; $payment->{source} = $payment->{"source_$count"}; $payment->reverse; } } get_search_criteria($payment); } sub check_job { my ($request) = @_; my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); $payment->check_job; my $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'check_job', format => 'HTML', ); $template->render($payment); } sub post_payments_bulk { my ($request) = @_; my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); $payment->post_bulk(); my $template; if ($payment->{queue_payments}){ $payment->{job_label} = 'Payments'; $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'check_job', format => 'HTML', ); } else { payments($request); } $template->render($payment); } sub display_payments { my ($request) = @_; my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); $payment->get_payment_detail_data(); $payment->debug({file => '/tmp/delme'}); for (@{$payment->{contact_invoices}}){ $_->{total_due} = $payment->format_amount(amount => $_->{total_due}); } my $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'payments_detail', format => 'HTML', ); $template->render($payment); } =item payment This method is used to set the filter screen and prints it, using the TT2 system. (hopefully it will... ) =back =cut sub payment { my ($request) = @_; my $locale = $request->{_locale}; my $dbPayment = LedgerSMB::DBObject::Payment->new({'base' => $request}); # Lets get the project data... my @projectOptions; my @arrayOptions = $dbPayment->list_open_projects(); push @projectOptions, {}; #A blank field on the select box for my $ref (0 .. $#arrayOptions) { push @projectOptions, { value => $arrayOptions[$ref]->{id}."--".$arrayOptions[$ref]->{projectnumber}."--".$arrayOptions[$ref]->{description}, text => $arrayOptions[$ref]->{projectnumber}."--".$arrayOptions[$ref]->{description}}; } # Lets get the departments data... my @departmentOptions; my $role = $request->{type} eq 'receipt' ? 'P' : 'C'; @arrayOptions = $dbPayment->list_departments($role); push @departmentOptions, {}; # A blank field on the select box for my $ref (0 .. $#arrayOptions) { push @departmentOptions, { value => $arrayOptions[$ref]->{id}."--".$arrayOptions[$ref]->{description}, text => $arrayOptions[$ref]->{description}}; } # Lets get the customer or vendor :) my @vcOptions; @arrayOptions = $dbPayment->get_open_accounts(); for my $ref (0 .. $#arrayOptions) { push @vcOptions, { value => $arrayOptions[$ref]->{id}.'--'.$arrayOptions[$ref]->{name}, text => $arrayOptions[$ref]->{name}}; } # Lets get the open currencies (this uses the $dbPayment->{account_class} property) my @currOptions; @arrayOptions = $dbPayment->get_open_currencies(); for my $ref (0 .. $#arrayOptions) { push @currOptions, { value => $arrayOptions[$ref]->{payments_get_open_currencies}, text => $arrayOptions[$ref]->{payments_get_open_currencies} }; } # Lets build filter by period my $date = LedgerSMB::DBObject::Date->new({base => $request}); $date->build_filter_by_period($locale); # Lets set the data in a hash for the template system. :) my $select = { stylesheet => $request->{_user}->{stylesheet}, login => { name => 'login', value => $request->{_user}->{login} }, projects => { name => 'projects', options => \@projectOptions }, department => { name => 'department', options => \@departmentOptions }, vendor_customer => { name => 'vendor-customer', options => \@vcOptions }, curr => { name => 'curr', options => \@currOptions }, month => { name => 'month', options => $date->{monthsOptions} }, year => { name => 'year', options => $date->{yearsOptions} }, interval_radios => $date->{radioOptions}, amountfrom => { name => 'amountfrom', }, amountto => { name => 'amountto', }, accountclass => { name => 'account_class', value => $dbPayment->{account_class} }, type => { name => 'type', value => $request->{type} }, action => { name => 'action', value => 'payment2', text => $locale->text("Continue"), }, }; # Lets call upon the template system my $template; $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'payment1', format => 'HTML', ); $template->render($select);# And finally, Lets print the screen :) } =pod =item payment2 This method is used for the payment module, it is a consecuence of the payment sub, and its used for all the mechanics of an invoices payment module. =back =cut sub payment2 { my ($request) = @_; my $locale = $request->{_locale}; my $Payment = LedgerSMB::DBObject::Payment->new({'base' => $request}); # VARIABLES my ($project_id, $project_number, $project_name, $department_id, $department_name ); my @array_options; my @project; my @selected_checkboxes; my @department; my @array_options; my @currency_options; my $exchangerate; # LETS GET THE CUSTOMER/VENDOR INFORMATION # TODO TODO TODO TODO TODO TODO TODO ($Payment->{entity_id}, $Payment->{company_name}) = split /--/ , $request->{'vendor-customer'}; # WE NEED TO RETRIEVE A BILLING LOCATION, THIS IS HARDCODED FOR NOW... Should we change it? $Payment->{location_class_id} = '1'; #$request->error($Payment->{entity_id}); my @vc_options; @vc_options = $Payment->get_vc_info(); # TODO TODO TODO TODO TODO TODO TODO # LETS BUILD THE PROJECTS INFO # I DONT KNOW IF I NEED ALL THIS, BUT AS IT IS AVAILABLE I'LL STORE IT FOR LATER USAGE. if ($request->{projects}) { ($project_id, $project_number, $project_name) = split /--/ , $request->{projects} ; @project = { name => 'projects', text => $project_number.' '.$project_name, value => $request->{projects}}; } # LETS GET THE DEPARTMENT INFO # WE HAVE TO SET $dbPayment->{department_id} NOW, THIS DATA WILL BE USED LATER WHEN WE # CALL FOR payment_get_open_invoices. :) if ($request->{department}) { ($Payment->{department_id}, $department_name) = split /--/, $request->{department}; @department = { name => 'department', text => $department_name, value => $request->{department}}; } # LETS GET ALL THE ACCOUNTS my @account_options = $Payment->list_accounting(); # LETS GET THE POSSIBLE SOURCES my @sources_options = $Payment->get_sources(\%$locale); # WE MUST PREPARE THE ENTITY INFORMATION @array_options = $Payment->get_vc_info(); # LETS BUILD THE CURRENCIES INFORMATION # FIRST, WE NEED TO KNOW THE DEFAULT CURRENCY my $default_currency = $Payment->get_default_currency(); # LETS BUILD THE COLUMN HEADERS WE ALWAYS NEED # THE OTHER HEADERS WILL BE BUILT IF THE RIGHT CONDITIONS ARE MET. # ----------------------------------------------- # SOME USERS WONT USE MULTIPLE CURRENCIES, AND WONT LIKE THE FACT CURRENCY BEING # ON THE SCREEN ALL THE TIME, SO IF THEY ARE USING THE DEFAULT CURRENCY WE WONT PRINT IT my $currency_text = $request->{curr} eq $default_currency ? '' : '('.$request->{curr}.')'; my $default_currency_text = $currency_text ? '('.$default_currency.')' : ''; my @column_headers = ({text => $locale->text('Invoice')}, {text => $locale->text('Date')}, {text => $locale->text('Total').$default_currency_text}, {text => $locale->text('Paid').$default_currency_text}, {text => $locale->text('Amount Due').$default_currency_text}, {text => $locale->text('To pay').$default_currency_text} ); # WE NEED TO KNOW IF WE ARE USING A CURRENCY THAT NEEDS AN EXCHANGERATE if ($default_currency ne $request->{curr} ) { # FIRST WE PUSH THE OTHER COLUMN HEADERS WE NEED push @column_headers, {text => $locale->text('Exchange Rate')}, {text => $locale->text('Amount Due').$currency_text}, {text => $locale->text('To pay').$currency_text}; # WE SET THEM IN THE RIGHT ORDER FOR THE TABLE INSIDE THE UI @column_headers[5,6,7] = @column_headers[6,7,5]; # DOES THE CURRENCY IN USE HAS AN EXCHANGE RATE?, IF SO # WE MUST SET THE VALUE, OTHERWISE THE UI WILL HANDLE IT $exchangerate = $request->{exrate} ? $request->{exrate} : $Payment->get_exchange_rate($request->{curr}, $request->{datepaid} ? $request->{datepaid} : $Payment->{current_date}); if ($exchangerate) { @currency_options = { name => 'exrate', value => "$exchangerate", #THERE IS A STRANGE BEHAVIOUR WITH THIS, text => "$exchangerate" #IF I DONT USE THE DOUBLE QUOTES, IT WILL PRINT THE ADDRESS #THERE MUST BE A REASON FOR THIS, I MUST RETURN TO IT LATER }; } else { @currency_options = { name => 'exrate'}; } } else { # WE MUST SET EXCHANGERATE TO 1 FOR THE MATHS SINCE WE # ARE USING THE DEFAULT CURRENCY $exchangerate = 1; @currency_options = { name => 'exrate', value => 1, text => 1 }; } # FINALLY WE ADD TO THE COLUMN HEADERS A LAST FIELD TO PRINT THE CLOSE INVOICE CHECKBOX TRICK :) push @column_headers, {text => 'X'}; # WE NEED TO QUERY THE DATABASE TO CHECK FOR OPEN INVOICES # WE WONT DO ANYTHING IF WE DONT FIND ANY INVOICES, THE USER CAN STILL POST A PREPAYMENT my @invoice_data; my @topay_state; # WE WILL USE THIS TO HELP UI TO DETERMINE WHAT IS VISIBLE @array_options = $Payment->get_open_invoices(); for my $ref (0 .. $#array_options) { if ( !$request->{"checkbox_$array_options[$ref]->{invoice_id}"}) { # LETS SET THE EXCHANGERATE VALUES my $due_fx; my $topay_fx_value; if ("$exchangerate") { $topay_fx_value = $due_fx = "$array_options[$ref]->{due}"/"$exchangerate"; } else { $topay_fx_value = $due_fx = "N/A"; } push @invoice_data, { invoice => { number => $array_options[$ref]->{invnumber}, id => $array_options[$ref]->{invoice_id}, href => 'ar.pl?id='."$array_options[$ref]->{invoice_id}" }, invoice_date => "$array_options[$ref]->{invoice_date}", amount => "$array_options[$ref]->{amount}", due => "$array_options[$ref]->{due}", paid => "$array_options[$ref]->{amount}" - "$array_options[$ref]->{due}", exchange_rate => "$exchangerate", due_fx => $due_fx, # This was set at the begining of the for statement topay => "$array_options[$ref]->{due}", source_text => $request->{"source_text_$array_options[$ref]->{invoice_id}"}, optional => $request->{"optional_pay_$array_options[$ref]->{invoice_id}"}, selected_account => $request->{"account_$array_options[$ref]->{invoice_id}"}, selected_source => $request->{"source_$array_options[$ref]->{invoice_id}"}, topay_fx => { name => "topay_fx_$array_options[$ref]->{invoice_id}", value => $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} ? $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} eq 'N/A' ? $topay_fx_value : $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} : $topay_fx_value # Ugly hack, but works ;) ... }#END HASH };# END PUSH push @topay_state, { id => "topaystate_$array_options[$ref]->{invoice_id}", value => $request->{"topaystate_$array_options[$ref]->{invoice_id}"} }; #END PUSH } else { push @selected_checkboxes, {name => "checkbox_$array_options[$ref]->{invoice_id}", value => "checked"} ; } #END IF }# END FOR # And finally, we are going to store the information for the overpayment / prepayment / advanced payment # and all the stuff, this is only needed for the update function. my @overpayment; my @overpayment_account; # Got to build the account selection box first. my @overpayment_account = $Payment->list_overpayment_accounting(); # Now we build the structure for the UI for (my $i=1 ; $i <= $request->{overpayment_qty}; $i++) { if (!$request->{"overpayment_checkbox_$i"}) { if ( $request->{"overpayment_topay_$i"} ) { # Now we split the account selected options my ($id, $accno, $description) = split(/--/, $request->{"overpayment_account_$i"}); push @overpayment, {amount => $request->{"overpayment_topay_$i"}, source1 => $request->{"overpayment_source1_$i"}, source2 => $request->{"overpayment_source2_$i"}, account => { id => $id, accno => $accno, description => $description } }; } else { $i = $request->{overpayment_qty} + 1; } } } # LETS BUILD THE SELECTION FOR THE UI my $select = { stylesheet => $request->{_user}->{stylesheet}, header => { text => $request->{type} eq 'receipt' ? $locale->text('Receipt') : $locale->text('Payment') }, login => { name => 'login', value => $request->{_user}->{login} }, accountclass => { name => 'account_class', value => $Payment->{account_class} }, project => @project ? @project : '' , # WE NEED TO VERIFY THAT THE ARRAY EXISTS, IF IT DOESNT, department => @department ? @department : '', # WE WILL PASS A NULL STRING, THIS FIXES THE ISSUES # I WAS HAVING WITH THE NULL ARRAYS, STILL UGLY :P account => \@account_options, selected_account => $request->{account}, datepaid => { name => 'datepaid', value => $request->{datepaid} ? $request->{datepaid} : $Payment->{current_date} }, source => \@sources_options, selected_source => $request->{source}, source_value => $request->{source_value}, defaultcurrency => { text => $default_currency }, curr => { name => 'curr', value => $request->{curr}, }, column_headers => \@column_headers, rows => \@invoice_data, topay_state => \@topay_state, vendorcustomer => { name => 'vendor-customer', value => $request->{'vendor-customer'} }, vc => { name => $Payment->{company_name}, # We will assume that the first Billing Information as default address => [ {text => $vc_options[0]->{'line_one'}}, {text => $vc_options[0]->{'line_two'}}, {text => $vc_options[0]->{'line_three'}}, {text => $vc_options[0]->{city}}, {text => $vc_options[0]->{state}}, {text => $vc_options[0]->{country}}] }, format => { name => 'FORMAT', options => [ {value => 1, text => "HTML" }, {value => 2, text => "PDF" }, {value => 3, text => "POSTSCRIPT" } ], }, media => { name => 'MEDIA', options => [ {value => 1, text => "Screen" }, {value => 2, text => "PRINTER" }, {value => 3, text => "EMAIL" } ], }, exrate => @currency_options, selectedcheckboxes => @selected_checkboxes ? \@selected_checkboxes : '', notes => $request->{notes}, overpayment => \@overpayment, overpayment_account => \@overpayment_account }; my $template = LedgerSMB::Template->new( user => $request->{_user}, locale => $request->{_locale}, path => 'UI/payments', template => 'payment2', format => 'HTML' ); eval {$template->render($select) }; if ($@) { $request->error("$@"); } # PRINT ERRORS ON THE UI } eval { do "scripts/custom/payment.pl"}; 1;