summaryrefslogtreecommitdiff
path: root/scripts/payment.pl
blob: 8007a175a5aa9f6eb7a36c3f597ae35c27efd324 (plain)
  1. =pod
  2. =head1 NAME
  3. LedgerSMB::Scripts::payment - LedgerSMB class defining the Controller functions for payment handling.
  4. =head1 SYNOPSIS
  5. Defines the controller functions and workflow logic for payment processing.
  6. =head1 COPYRIGHT
  7. Portions Copyright (c) 2007, David Mora R and Christian Ceballos B.
  8. Licensed to the public under the terms of the GNU GPL version 2 or later.
  9. Original copyright notice below.
  10. #=====================================================================
  11. # PLAXIS
  12. # Copyright (c) 2007
  13. #
  14. # Author: David Mora R
  15. # Christian Ceballos B
  16. #
  17. #
  18. #
  19. #
  20. #
  21. # This program is free software; you can redistribute it and/or modify
  22. # it under the terms of the GNU General Public License as published by
  23. # the Free Software Foundation; either version 2 of the License, or
  24. # (at your option) any later version.
  25. #
  26. # This program is distributed in the hope that it will be useful,
  27. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. # GNU General Public License for more details.
  30. # You should have received a copy of the GNU General Public License
  31. # along with this program; if not, write to the Free Software
  32. # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  33. =head1 METHODS
  34. =cut
  35. package LedgerSMB::Scripts::payment;
  36. use LedgerSMB::Template;
  37. use LedgerSMB::Sysconfig;
  38. use LedgerSMB::DBObject::Payment;
  39. use LedgerSMB::DBObject::Date;
  40. use Error::Simple;
  41. use strict;
  42. # CT: A few notes for future refactoring of this code:
  43. # 1: I don't think it is a good idea to make the UI too dependant on internal
  44. # code structures but I don't see a good alternative at the moment.
  45. # 2: CamelCasing: -1
  46. =pod
  47. =item payment
  48. This method is used to set the filter screen and prints it, using the
  49. TT2 system.
  50. =back
  51. =cut
  52. sub payments {
  53. my ($request) = @_;
  54. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  55. $payment->get_metadata();
  56. if (!defined $payment->{batch_date}){
  57. $payment->error("No Batch Date!");
  58. }
  59. my $template = LedgerSMB::Template->new(
  60. user => $request->{_user},
  61. locale => $request->{_locale},
  62. path => 'UI/payments',
  63. template => 'payments_filter',
  64. format => 'HTML',
  65. );
  66. $template->render($payment);
  67. }
  68. sub get_search_criteria {
  69. my ($request) = @_;
  70. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  71. $payment->get_metadata();
  72. if ($payment->{batch_id} && $payment->{batch_date}){
  73. $payment->{date_reversed} = $payment->{batch_date};
  74. }
  75. my $template = LedgerSMB::Template->new(
  76. user => $request->{_user},
  77. locale => $request->{_locale},
  78. path => 'UI/payments',
  79. template => 'search',
  80. format => 'HTML',
  81. );
  82. $template->render($payment);
  83. }
  84. sub get_search_results {
  85. my ($request) = @_;
  86. my $rows = [];
  87. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  88. my @search_results = $payment->search;
  89. my $template = LedgerSMB::Template->new(
  90. user => $request->{_user},
  91. locale => $request->{_locale},
  92. path => 'UI',
  93. template => 'form-dynatable',
  94. format => ($payment->{format}) ? $payment->{format} : 'HTML',
  95. );
  96. my $base_url = "payment.pl?";
  97. my $search_url = "$base_url";
  98. for my $key (keys %{$request->take_top_level}){
  99. if ($base_url =~ /\?$/){
  100. $base_url .= "$key=$request->{key}";
  101. } else {
  102. $base_url .= "&$key=$request->{key}";
  103. }
  104. }
  105. my @columns = qw(selected meta_number date_paid amount source company_paid);
  106. my $contact_type = ($payment->{account_class} == 1) ? 'Vendor' : 'Customer';
  107. # CT: Locale strings for gettext:
  108. # $request->{_locale}->text("Vendor Number");
  109. # $request->{_locale}->text("Customer Number");
  110. my $heading = {
  111. selected => $request->{_locale}->text('Selected'),
  112. company_paid => {
  113. text => $request->{_locale}->text('Company Name'),
  114. href => "$search_url&orderby=company_paid",
  115. },
  116. meta_number => {
  117. text => $request->{_locale}->text(
  118. "$contact_type Number"
  119. ),
  120. href => "$search_url&orderby=meta_number",
  121. },
  122. date_paid => {
  123. text => $request->{_locale}->text('Date Paid'),
  124. href => "$search_url&orderby=date_paid",
  125. },
  126. amount => {
  127. text => $request->{_locale}->text('Total Paid'),
  128. href => "$search_url&orderby=amount",
  129. },
  130. source => {
  131. text => $request->{_locale}->text('Source'),
  132. href => "$search_url&orderby=source",
  133. },
  134. };
  135. my $classcount;
  136. $classcount = 0;
  137. my $rowcount;
  138. $rowcount = 1;
  139. for my $line (@search_results){
  140. $classcount ||= 0;
  141. $rowcount += 1;
  142. push(@$rows, {
  143. company_paid => $line->{company_paid},
  144. amount => $request->format_amount(amount => $line->{amount}),
  145. i => "$classcount",
  146. date_paid => $line->{date_paid},
  147. source => $line->{source},
  148. meta_number => $line->{meta_number},
  149. selected => {
  150. input => {
  151. type => "checkbox",
  152. name => "payment_$rowcount",
  153. value => "1",
  154. },
  155. }
  156. });
  157. $payment->{"credit_id_$rowcount"} = $line->{credit_id};
  158. $payment->{"date_paid_$rowcount"} = $line->{date_paid};
  159. $payment->{"source_$rowcount"} = $line->{source};
  160. $classcount = ($classcount + 1) % 2;
  161. ++$rowcount;
  162. }
  163. $payment->{rowcount} = $rowcount;
  164. $payment->{script} = 'payment.pl';
  165. $payment->{title} = $request->{_locale}->text("Payment Results");
  166. my $hiddens = $payment->take_top_level;
  167. $template->render({
  168. form => $payment,
  169. columns => \@columns,
  170. heading => $heading,
  171. hiddens => $payment->take_top_level,
  172. rows => $rows,
  173. buttons => [{
  174. value => 'reverse_payments',
  175. name => 'action',
  176. class => 'submit',
  177. type => 'submit',
  178. text => $request->{_locale}->text('Reverse Payments'),
  179. }]
  180. });
  181. }
  182. sub get_search_results_reverse_payments {
  183. my ($request) = @_;
  184. my $payment = LedgerSMB::DBObject::Payment->new({base => $request});
  185. for my $count (1 .. $payment->{rowcount}){
  186. if ($payment->{"payment_$count"}){
  187. $payment->{credit_id} = $payment->{"credit_id_$count"};
  188. $payment->{date_paid} = $payment->{"date_paid_$count"};
  189. $payment->{source} = $payment->{"source_$count"};
  190. $payment->reverse;
  191. }
  192. }
  193. get_search_criteria($payment);
  194. }
  195. sub check_job {
  196. my ($request) = @_;
  197. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  198. $payment->check_job;
  199. my $template = LedgerSMB::Template->new(
  200. user => $request->{_user},
  201. locale => $request->{_locale},
  202. path => 'UI/payments',
  203. template => 'check_job',
  204. format => 'HTML',
  205. );
  206. $template->render($payment);
  207. }
  208. sub post_payments_bulk {
  209. my ($request) = @_;
  210. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  211. $payment->post_bulk();
  212. my $template;
  213. if ($payment->{queue_payments}){
  214. $payment->{job_label} = 'Payments';
  215. $template = LedgerSMB::Template->new(
  216. user => $request->{_user},
  217. locale => $request->{_locale},
  218. path => 'UI/payments',
  219. template => 'check_job',
  220. format => 'HTML',
  221. );
  222. } else {
  223. payments($request);
  224. }
  225. $template->render($payment);
  226. }
  227. sub print {
  228. use LedgerSMB::DBObject::Company;
  229. use LedgerSMB::Batch;
  230. my ($request) = @_;
  231. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  232. $payment->{company} = $payment->{_user}->{company};
  233. $payment->{address} = $payment->{_user}->{address};
  234. my $template;
  235. if ($payment->{batch_id}){
  236. my $batch = LedgerSMB::Batch->new(
  237. {base => $payment,
  238. copy => 'base' }
  239. );
  240. $batch->{batch_id} = $payment->{batch_id};
  241. $batch->get;
  242. $payment->{batch_description} = $batch->{description};
  243. $payment->{batch_control_code} = $batch->{control_code};
  244. }
  245. $payment->{format_amount} = sub {return $payment->format_amount(@_); };
  246. if ($payment->{multiple}){
  247. $payment->{checks} = [];
  248. for my $line (1 .. $payment->{contact_count}){
  249. my $id = $payment->{"contact_$line"};
  250. next if !defined $payment->{"id_$id"};
  251. my $check = LedgerSMB::DBObject::Company->new(
  252. {base => $request, copy => 'base' }
  253. );
  254. $check->{entity_class} = $payment->{account_class};
  255. $check->{id} = $id;
  256. $check->get_billing_info;
  257. $check->{amount} = $check->parse_amount(amount => '0');
  258. $check->{invoices} = [];
  259. $check->{source} = $payment->{"source_$id"};
  260. my $inv_count;
  261. if ($LedgerSMB::Sysconfig::check_max_invoices >
  262. $payment->{"invoice_count_$id"})
  263. {
  264. $inv_count = $payment->{"invoice_count_$id"};
  265. } else {
  266. $inv_count = $LedgerSMB::Sysconfig::check_max_invoices;
  267. }
  268. for my $inv (1 .. $inv_count){
  269. my $invhash = {};
  270. my $inv_id = $payment->{"invoice_${id}_$inv"};
  271. for (qw(invnumber invdate)){
  272. $invhash->{$_} = $payment->{"${_}_$inv_id"};
  273. }
  274. if ($payment->{"paid_$id"} eq 'some'){
  275. $invhash->{paid} = $payment->parse_amount(amount => $payment->{"payment_$inv_id"});
  276. } elsif ($payment->{"paid_$id"} eq 'all'){
  277. $invhash->{paid} = $payment->parse_amount(amount => $payment->{"net_$inv_id"});
  278. } else {
  279. $payment->error("Invalid Payment Amount Option");
  280. }
  281. $check->{amount} += $invhash->{paid};
  282. $invhash->{paid} = $check->format_amount(amount => $invhash->{paid});
  283. push @{$check->{invoices}}, $invhash;
  284. }
  285. my $amt = $check->{amount}->copy;
  286. $amt->bfloor();
  287. $check->{text_amount} = $payment->text_amount($amt);
  288. $check->{amount} = $check->format_amount(amount => $check->{amount},
  289. format => '1000.00');
  290. $check->{decimal} = $check->format_amount(amount => ($check->{amount} - $amt) * 100);
  291. push @{$payment->{checks}}, $check;
  292. }
  293. $template = LedgerSMB::Template->new(
  294. user => $payment->{_user}, template => 'check_multiple',
  295. format => uc $payment->{'format'},
  296. no_auto_output => 1,
  297. output_args => $payment,
  298. );
  299. #try {
  300. $template->render($payment);
  301. $template->output(%$payment);
  302. #}
  303. #catch Error::Simple with {
  304. # my $E = shift;
  305. # $payment->error( $E->stacktrace );
  306. #};
  307. display_payments(@_);
  308. } else {
  309. }
  310. }
  311. sub update_payments {
  312. display_payments(@_);
  313. }
  314. sub display_payments {
  315. my ($request) = @_;
  316. my $payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  317. $payment->get_payment_detail_data();
  318. $payment->{grand_total} = 0;
  319. for (@{$payment->{contact_invoices}}){
  320. my $contact_total = 0;
  321. $_->{total_due} = $payment->format_amount(amount => $_->{total_due},
  322. money => 1);
  323. for my $invoice (@{$_->{invoices}}){
  324. if (($payment->{action} ne 'update_payments')
  325. or (defined $payment->{"id_$_->{contact_id}"})){
  326. if ($payment->{"paid_$_->{contact_id}"} eq 'some'){
  327. my $i_id = $invoice->[0];
  328. $contact_total
  329. += $payment->{"payment_$i_id"};
  330. }
  331. }
  332. $invoice->[3] = $payment->format_amount(amount => $invoice->[3],
  333. money => 1);
  334. $invoice->[4] = $payment->format_amount(amount => $invoice->[4],
  335. money => 1);
  336. $invoice->[5] = $payment->format_amount(amount => $invoice->[5],
  337. money => 1);
  338. $invoice->[6] = $payment->format_amount(amount => $invoice->[6],
  339. money => 1);
  340. my $fld = "payment_" . $invoice->[0];
  341. if (!defined $payment->{"$fld"} ){
  342. $payment->{"$fld"} = $invoice->[6];
  343. }
  344. }
  345. if ($payment->{"paid_$_->{contact_id}"} ne 'some') {
  346. $contact_total = $_->{total_due};
  347. }
  348. if (($payment->{action} ne 'update_payments')
  349. or (defined $payment->{"id_$_->{contact_id}"})){
  350. $_->{contact_total} = $contact_total;
  351. $payment->{grand_total} += $contact_total;
  352. }
  353. }
  354. @{$payment->{media_options}} = (
  355. {text => $request->{_locale}->text('Screen'),
  356. value => 'screen'});
  357. for (keys %LedgerSMB::Sysconfig::printer){
  358. push @{$payment->{media_options}},
  359. {text => $_,
  360. value => $_};
  361. }
  362. if ($LedgerSMB::Sysconfig::latex){
  363. @{$payment->{format_options}} = (
  364. {text => 'PDF', value => 'PDF'},
  365. {text => 'Postscript', value => 'Postscript'},
  366. );
  367. $payment->{can_print} = 1;
  368. }
  369. my $template = LedgerSMB::Template->new(
  370. user => $request->{_user},
  371. locale => $request->{_locale},
  372. path => 'UI/payments',
  373. template => 'payments_detail',
  374. format => 'HTML',
  375. );
  376. $template->render($payment);
  377. }
  378. =item payment
  379. This method is used to set the filter screen and prints it, using the
  380. TT2 system.
  381. =back
  382. =cut
  383. sub payment {
  384. my ($request) = @_;
  385. my $locale = $request->{_locale};
  386. my $dbPayment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  387. # Lets get the project data...
  388. my @projectOptions;
  389. my @arrayOptions = $dbPayment->list_open_projects();
  390. push @projectOptions, {}; #A blank field on the select box
  391. for my $ref (0 .. $#arrayOptions) {
  392. push @projectOptions, { value => $arrayOptions[$ref]->{id}."--".$arrayOptions[$ref]->{projectnumber}."--".$arrayOptions[$ref]->{description},
  393. text => $arrayOptions[$ref]->{projectnumber}."--".$arrayOptions[$ref]->{description}};
  394. }
  395. # Lets get the departments data...
  396. my @departmentOptions;
  397. my $role = $request->{type} eq 'receipt' ? 'P' : 'C';
  398. @arrayOptions = $dbPayment->list_departments($role);
  399. push @departmentOptions, {}; # A blank field on the select box
  400. for my $ref (0 .. $#arrayOptions) {
  401. push @departmentOptions, { value => $arrayOptions[$ref]->{id}."--".$arrayOptions[$ref]->{description},
  402. text => $arrayOptions[$ref]->{description}};
  403. }
  404. # Lets get the currencies (this uses the $dbPayment->{account_class} property)
  405. my @currOptions;
  406. @arrayOptions = $dbPayment->get_open_currencies();
  407. for my $ref (0 .. $#arrayOptions) {
  408. push @currOptions, { value => $arrayOptions[$ref]->{payments_get_open_currencies},
  409. text => $arrayOptions[$ref]->{payments_get_open_currencies} };
  410. }
  411. # Lets build filter by period
  412. my $date = LedgerSMB::DBObject::Date->new({base => $request});
  413. $date->build_filter_by_period($locale);
  414. # Lets set the data in a hash for the template system. :)
  415. my $select = {
  416. stylesheet => $request->{_user}->{stylesheet},
  417. login => { name => 'login',
  418. value => $request->{_user}->{login} },
  419. projects => {
  420. name => 'projects',
  421. options => \@projectOptions
  422. },
  423. department => {
  424. name => 'department',
  425. options => \@departmentOptions
  426. },
  427. curr => {
  428. name => 'curr',
  429. options => \@currOptions
  430. },
  431. month => {
  432. name => 'month',
  433. options => $date->{monthsOptions}
  434. },
  435. year => {
  436. name => 'year',
  437. options => $date->{yearsOptions}
  438. },
  439. interval_radios => $date->{radioOptions},
  440. amountfrom => {
  441. name => 'amountfrom',
  442. },
  443. amountto => {
  444. name => 'amountto',
  445. },
  446. accountclass => {
  447. name => 'account_class',
  448. value => $dbPayment->{account_class}
  449. },
  450. type => {
  451. name => 'type',
  452. value => $request->{type}
  453. },
  454. action => {
  455. name => 'action',
  456. value => 'payment1_5',
  457. text => $locale->text("Continue"),
  458. }
  459. };
  460. my $template;
  461. $template = LedgerSMB::Template->new(
  462. user => $request->{_user},
  463. locale => $request->{_locale},
  464. path => 'UI/payments',
  465. template => 'payment1',
  466. format => 'HTML' );
  467. $template->render($select);# And finally, Lets print the screen :)
  468. }
  469. =pod
  470. =item payment1_5
  471. This method is called between payment and payment2, it will search the database
  472. for entity_credit_accounts that match the parameter, if only one is found it will
  473. run unnoticed by the user, if more than one is found it will ask the user to pick
  474. one to handle the payment against
  475. =back
  476. =cut
  477. sub payment1_5 {
  478. my ($request) = @_;
  479. my $locale = $request->{_locale};
  480. my $dbPayment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  481. my @array_options = $dbPayment->get_entity_credit_account();
  482. if ($#array_options == -1) {
  483. &payment($request);
  484. } elsif ($#array_options == 0) {
  485. $request->{'vendor-customer'} = $array_options[0]->{id}.'--'.$array_options[0]->{name};
  486. &payment2($request);
  487. } else {
  488. # Lets call upon the template system
  489. my @company_options;
  490. for my $ref (0 .. $#array_options) {
  491. push @company_options, { id => $array_options[$ref]->{id},
  492. name => $array_options[$ref]->{name}};
  493. }
  494. my $select = {
  495. companies => \@company_options,
  496. stylesheet => $request->{_user}->{stylesheet},
  497. login => { name => 'login',
  498. value => $request->{_user}->{login}},
  499. department => { name => 'department',
  500. value => $request->{department}},
  501. currency => { name => 'curr',
  502. value => $request->{curr}},
  503. datefrom => { name => 'datefrom',
  504. value => $request->{datefrom}},
  505. dateto => { name => 'dateto',
  506. value => $request->{dateto}},
  507. amountfrom => { name => 'amountfrom',
  508. value => $request->{datefrom}},
  509. amountto => { name => 'amountto',
  510. value => $request->{dateto}},
  511. accountclass => { name => 'account_class',
  512. value => $dbPayment->{account_class}},
  513. type => { name => 'type',
  514. value => $request->{type}},
  515. action => { name => 'action',
  516. value => 'payment2',
  517. text => $locale->text("Continue")}
  518. };
  519. my $template;
  520. $template = LedgerSMB::Template->new(
  521. user => $request->{_user},
  522. locale => $request->{_locale},
  523. path => 'UI/payments',
  524. template => 'payment1_5',
  525. format => 'HTML' );
  526. eval {$template->render($select) };
  527. if ($@) { $request->error("$@"); } # PRINT ERRORS ON THE UI
  528. }
  529. }
  530. =pod
  531. =item payment2
  532. This method is used for the payment module, it is a consecuence of the payment sub,
  533. and its used for all the mechanics of an invoices payment module.
  534. =back
  535. =cut
  536. sub payment2 {
  537. my ($request) = @_;
  538. my $locale = $request->{_locale};
  539. my $Payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  540. # VARIABLES
  541. my ($project_id, $project_number, $project_name, $department_id, $department_name );
  542. my @array_options;
  543. my @project;
  544. my @selected_checkboxes;
  545. my @department;
  546. my @array_options;
  547. my @currency_options;
  548. my $exchangerate;
  549. # LETS GET THE CUSTOMER/VENDOR INFORMATION
  550. ($Payment->{entity_credit_id}, $Payment->{company_name}) = split /--/ , $request->{'vendor-customer'};
  551. # WE NEED TO RETRIEVE A BILLING LOCATION, THIS IS HARDCODED FOR NOW... Should we change it?
  552. $Payment->{location_class_id} = '1';
  553. my @vc_options;
  554. @vc_options = $Payment->get_vc_info();
  555. # LETS BUILD THE PROJECTS INFO
  556. # I DONT KNOW IF I NEED ALL THIS, BUT AS IT IS AVAILABLE I'LL STORE IT FOR LATER USAGE.
  557. if ($request->{projects}) {
  558. ($project_id, $project_number, $project_name) = split /--/ , $request->{projects} ;
  559. @project = { name => 'projects', text => $project_number.' '.$project_name, value => $request->{projects}};
  560. }
  561. # LETS GET THE DEPARTMENT INFO
  562. # WE HAVE TO SET $dbPayment->{department_id} NOW, THIS DATA WILL BE USED LATER WHEN WE
  563. # CALL FOR payment_get_open_invoices. :)
  564. if ($request->{department}) {
  565. ($Payment->{department_id}, $department_name) = split /--/, $request->{department};
  566. @department = { name => 'department', text => $department_name, value => $request->{department}};
  567. }
  568. # LETS GET ALL THE ACCOUNTS
  569. my @account_options = $Payment->list_accounting();
  570. # LETS GET THE POSSIBLE SOURCES
  571. my @sources_options = $Payment->get_sources(\%$locale);
  572. # LETS BUILD THE CURRENCIES INFORMATION
  573. # FIRST, WE NEED TO KNOW THE DEFAULT CURRENCY
  574. my $default_currency = $Payment->get_default_currency();
  575. # LETS BUILD THE COLUMN HEADERS WE ALWAYS NEED
  576. # THE OTHER HEADERS WILL BE BUILT IF THE RIGHT CONDITIONS ARE MET.
  577. # -----------------------------------------------
  578. # SOME USERS WONT USE MULTIPLE CURRENCIES, AND WONT LIKE THE FACT CURRENCY BEING
  579. # ON THE SCREEN ALL THE TIME, SO IF THEY ARE USING THE DEFAULT CURRENCY WE WONT PRINT IT
  580. my $currency_text = $request->{curr} eq $default_currency ? '' : '('.$request->{curr}.')';
  581. my $default_currency_text = $currency_text ? '('.$default_currency.')' : '';
  582. my @column_headers = ({text => $locale->text('Invoice')},
  583. {text => $locale->text('Date')},
  584. {text => $locale->text('Total').$default_currency_text},
  585. {text => $locale->text('Paid').$default_currency_text},
  586. {text => $locale->text('Discount').$default_currency_text},
  587. {text => $locale->text('Apply Disc')},
  588. {text => $locale->text('Memo')},
  589. {text => $locale->text('Amount Due').$default_currency_text}
  590. );
  591. # WE NEED TO KNOW IF WE ARE USING A CURRENCY THAT NEEDS AN EXCHANGERATE
  592. if ($default_currency ne $request->{curr} ) {
  593. # FIRST WE PUSH THE OTHER COLUMN HEADERS WE NEED
  594. push @column_headers, {text => $locale->text('Exchange Rate')},
  595. {text => $locale->text('Amount Due').$currency_text},
  596. {text => $locale->text('To pay').$currency_text};
  597. # WE SET THEM IN THE RIGHT ORDER FOR THE TABLE INSIDE THE UI
  598. @column_headers[7,8] = @column_headers[8,7];
  599. # DOES THE CURRENCY IN USE HAS AN EXCHANGE RATE?, IF SO
  600. # WE MUST SET THE VALUE, OTHERWISE THE UI WILL HANDLE IT
  601. $exchangerate = $request->{exrate} ?
  602. $request->{exrate} :
  603. $Payment->get_exchange_rate($request->{curr},
  604. $request->{datepaid} ? $request->{datepaid} : $Payment->{current_date});
  605. if ($exchangerate) {
  606. @currency_options = {
  607. name => 'exrate',
  608. value => "$exchangerate", #THERE IS A STRANGE BEHAVIOUR WITH THIS,
  609. text => "$exchangerate" #IF I DONT USE THE DOUBLE QUOTES, IT WILL PRINT THE ADDRESS
  610. #THERE MUST BE A REASON FOR THIS, I MUST RETURN TO IT LATER
  611. };
  612. } else {
  613. @currency_options = {
  614. name => 'exrate'};
  615. }
  616. } else {
  617. # WE MUST SET EXCHANGERATE TO 1 FOR THE MATHS SINCE WE
  618. # ARE USING THE DEFAULT CURRENCY
  619. $exchangerate = 1;
  620. @currency_options = {
  621. name => 'exrate',
  622. value => 1,
  623. text => 1
  624. };
  625. }
  626. # FINALLY WE ADD TO THE COLUMN HEADERS A LAST FIELD TO PRINT THE CLOSE INVOICE CHECKBOX TRICK :)
  627. push @column_headers, {text => 'X'};
  628. # WE NEED TO QUERY THE DATABASE TO CHECK FOR OPEN INVOICES
  629. # WE WONT DO ANYTHING IF WE DONT FIND ANY INVOICES, THE USER CAN STILL POST A PREPAYMENT
  630. my @invoice_data;
  631. my @topay_state; # WE WILL USE THIS TO HELP UI TO DETERMINE WHAT IS VISIBLE
  632. @array_options = $Payment->get_open_invoices();
  633. my $unhandled_overpayment;
  634. for my $ref (0 .. $#array_options) {
  635. if ( !$request->{"checkbox_$array_options[$ref]->{invoice_id}"}) {
  636. # SHOULD I APPLY DISCCOUNTS?
  637. $request->{"optional_discount_$array_options[$ref]->{invoice_id}"} = $request->{first_load}? "on": $request->{"optional_discount_$array_options[$ref]->{invoice_id}"};
  638. # LETS SET THE EXCHANGERATE VALUES
  639. my $due_fx = $request->{"optional_discount_$array_options[$ref]->{invoice_id}"} ? $request->round_amount($array_options[$ref]->{due_fx}) : $request->round_amount($array_options[$ref]->{due_fx}) + $array_options[$ref]->{discount_fx} ;
  640. my $topay_fx_value;
  641. if ("$exchangerate") {
  642. $topay_fx_value = $due_fx;
  643. if (!$request->{"optional_discount_$array_options[$ref]->{invoice_id}"}) {
  644. $topay_fx_value = $due_fx = $due_fx + $request->round_amount($array_options[$ref]->{discount}/$array_options[$ref]->{exchangerate});
  645. }
  646. } else {
  647. $topay_fx_value = "N/A";
  648. }
  649. # We need to check for unhandled overpayment, see the post function for details
  650. # First we will see if the discount should apply?
  651. =i dont think this is working
  652. my $temporary_discount = 0;
  653. if (($request->{"optional_discount_$array_options[$ref]->{invoice_id}"})&&($due_fx <= $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} + $request->round_amount($array_options[$ref]->{discount}/"$exchangerate"))) {
  654. $temporary_discount = $request->round_amount("$array_options[$ref]->{discount}"/$array_options[$ref]->{exchangerate});
  655. }
  656. =cut
  657. # We need to compute the unhandled_overpayment, notice that all the values inside the if already have
  658. # the exchangerate applied
  659. if ( $due_fx < $request->{"topay_fx_$array_options[$ref]->{invoice_id}"}) {
  660. # We need to store all the overpayments so we can use it on the screen
  661. $unhandled_overpayment = $request->round_amount($unhandled_overpayment + $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} - $due_fx );
  662. $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} = "$due_fx";
  663. }
  664. #Now its time to build the link to the invoice :)
  665. my $uri = $Payment->{account_class} == 1 ? 'ap' : 'ar';
  666. $uri .= '.pl?action=edit&id='.$array_options[$ref]->{invoice_id}.'&path=bin/mozilla&login='.$request->{login};
  667. push @invoice_data, { invoice => { number => $array_options[$ref]->{invnumber},
  668. id => $array_options[$ref]->{invoice_id},
  669. href => $uri
  670. },
  671. invoice_date => "$array_options[$ref]->{invoice_date}",
  672. amount => "$array_options[$ref]->{amount}",
  673. due => $request->{"optional_discount_$array_options[$ref]->{invoice_id}"}? "$array_options[$ref]->{due}" : "$array_options[$ref]->{due}" + "$array_options[$ref]->{discount}",
  674. paid => "$array_options[$ref]->{amount}" - "$array_options[$ref]->{due}"-"$array_options[$ref]->{discount}",
  675. discount => $request->{"optional_discount_$array_options[$ref]->{invoice_id}"} ? "$array_options[$ref]->{discount}" : 0 ,
  676. optional_discount => $request->{"optional_discount_$array_options[$ref]->{invoice_id}"},
  677. exchange_rate => "$array_options[$ref]->{exchangerate}",
  678. due_fx => "$due_fx", # This was set at the begining of the for statement
  679. topay => "$array_options[$ref]->{due}" - "$array_options[$ref]->{discount}",
  680. source_text => $request->{"source_text_$array_options[$ref]->{invoice_id}"},
  681. optional => $request->{"optional_pay_$array_options[$ref]->{invoice_id}"},
  682. selected_account => $request->{"account_$array_options[$ref]->{invoice_id}"},
  683. selected_source => $request->{"source_$array_options[$ref]->{invoice_id}"},
  684. memo => { name => "memo_invoice_$array_options[$ref]->{invoice_id}",
  685. value => $request->{"memo_invoice_$array_options[$ref]->{invoice_id}"}
  686. },#END HASH
  687. topay_fx => { name => "topay_fx_$array_options[$ref]->{invoice_id}",
  688. value => $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} ?
  689. $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} eq 'N/A' ?
  690. "$topay_fx_value" :
  691. $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} :
  692. "$topay_fx_value"
  693. # Ugly hack, but works ;) ...
  694. }#END HASH
  695. };# END PUSH
  696. push @topay_state, {
  697. id => "topaystate_$array_options[$ref]->{invoice_id}",
  698. value => $request->{"topaystate_$array_options[$ref]->{invoice_id}"}
  699. }; #END PUSH
  700. }
  701. else {
  702. push @selected_checkboxes, {name => "checkbox_$array_options[$ref]->{invoice_id}",
  703. value => "checked"} ;
  704. } #END IF
  705. }# END FOR
  706. # And finally, we are going to store the information for the overpayment / prepayment / advanced payment
  707. # and all the stuff, this is only needed for the update function.
  708. my @overpayment;
  709. my @overpayment_account;
  710. # Got to build the account selection box first.
  711. my @overpayment_account = $Payment->list_overpayment_accounting();
  712. # Now we build the structure for the UI
  713. for (my $i=1 ; $i <= $request->{overpayment_qty}; $i++) {
  714. if (!$request->{"overpayment_checkbox_$i"}) {
  715. if ( $request->{"overpayment_topay_$i"} ) {
  716. # Now we split the account selected options
  717. my ($id, $accno, $description) = split(/--/, $request->{"overpayment_account_$i"});
  718. my ($cashid, $cashaccno, $cashdescription ) = split(/--/, $request->{"overpayment_cash_account_$i"});
  719. push @overpayment, {amount => $request->{"overpayment_topay_$i"},
  720. source1 => $request->{"overpayment_source1_$i"},
  721. source2 => $request->{"overpayment_source2_$i"},
  722. memo => $request->{"overpayment_memo_$i"},
  723. account => { id => $id,
  724. accno => $accno,
  725. description => $description
  726. },
  727. cashaccount => { id => $cashid,
  728. accno => $cashaccno,
  729. description => $cashdescription
  730. }
  731. };
  732. } else {
  733. $i = $request->{overpayment_qty} + 1;
  734. }
  735. }
  736. }
  737. # We need to set the availible media and format from printing
  738. my @media_options;
  739. push @media_options, {value => 1, text => "Screen"};
  740. if ($#{LedgerSMB::Sysconfig::printer}) {
  741. for (keys %{LedgerSMB::Sysconfig::printer}) {
  742. push @media_options, {value => 1, text => $_};
  743. }
  744. }
  745. #$request->error("@media_options");
  746. my @format_options;
  747. push @format_options, {value => 1, text => "HTML"};
  748. if (${LedgerSMB::Sysconfig::latex}) {
  749. push @format_options, {value => 2, text => "PDF" }, {value => 3, text => "POSTSCRIPT" };
  750. }
  751. # LETS BUILD THE SELECTION FOR THE UI
  752. # Notice that the first data inside this selection is the firs_load, this
  753. # will help payment2.html to know wether it is beeing called for the first time
  754. my $select = {
  755. first_load => $request->{first_load},
  756. stylesheet => $request->{_user}->{stylesheet},
  757. header => { text => $request->{type} eq 'receipt' ? $locale->text('Receipt') : $locale->text('Payment') },
  758. type => { name => 'type',
  759. value => $request->{type} },
  760. login => { name => 'login',
  761. value => $request->{login} },
  762. accountclass => {
  763. name => 'account_class',
  764. value => $Payment->{account_class}
  765. },
  766. project => @project ? @project : '' , # WE NEED TO VERIFY THAT THE ARRAY EXISTS, IF IT DOESNT,
  767. department => @department ? @department : '', # WE WILL PASS A NULL STRING, THIS FIXES THE ISSUES
  768. # I WAS HAVING WITH THE NULL ARRAYS, STILL UGLY :P
  769. account => \@account_options,
  770. selected_account => $request->{account},
  771. datepaid => {
  772. name => 'datepaid',
  773. value => $request->{datepaid} ? $request->{datepaid} : $Payment->{current_date}
  774. },
  775. source => \@sources_options,
  776. selected_source => $request->{source},
  777. source_value => $request->{source_value},
  778. defaultcurrency => {
  779. text => $default_currency
  780. },
  781. curr => { name => 'curr',
  782. value => $request->{curr},
  783. },
  784. column_headers => \@column_headers,
  785. rows => \@invoice_data,
  786. topay_state => \@topay_state,
  787. vendorcustomer => { name => 'vendor-customer',
  788. value => $request->{'vendor-customer'}
  789. },
  790. unhandled_overpayment => { name => 'unhandledoverpayment', value => "$unhandled_overpayment" } ,
  791. vc => { name => $Payment->{company_name}, # We will assume that the first Billing Information as default
  792. address => [ {text => $vc_options[0]->{'line_one'}},
  793. {text => $vc_options[0]->{'line_two'}},
  794. {text => $vc_options[0]->{'line_three'}},
  795. {text => $vc_options[0]->{city}},
  796. {text => $vc_options[0]->{state}},
  797. {text => $vc_options[0]->{country}}]
  798. },
  799. format => {
  800. name => 'FORMAT',
  801. options => \@format_options
  802. },
  803. media => {
  804. name => 'MEDIA',
  805. options => \@media_options
  806. },
  807. exrate => @currency_options,
  808. selectedcheckboxes => @selected_checkboxes ? \@selected_checkboxes : '',
  809. notes => $request->{notes},
  810. overpayment => \@overpayment,
  811. overpayment_account => \@overpayment_account
  812. };
  813. my $template = LedgerSMB::Template->new(
  814. user => $request->{_user},
  815. locale => $request->{_locale},
  816. path => 'UI/payments',
  817. template => 'payment2',
  818. format => 'HTML' );
  819. eval {$template->render($select) };
  820. if ($@) { $request->error("$@"); } # PRINT ERRORS ON THE UI
  821. }
  822. =pod
  823. =item post_payment
  824. This method is used for the payment module (not the bulk payment),
  825. and its used for all the mechanics of storing a payment.
  826. =back
  827. =cut
  828. sub post_payment {
  829. my ($request) = @_;
  830. my $locale = $request->{_locale};
  831. my $Payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  832. if (!$request->{exrate}) {
  833. $Payment->error($locale->text('Exchange rate hasn\'t been defined').'!');}
  834. # LETS GET THE CUSTOMER/VENDOR INFORMATION
  835. ($Payment->{entity_credit_id}, $Payment->{company_name}) = split /--/ , $request->{'vendor-customer'};
  836. # LETS GET THE DEPARTMENT INFO
  837. # WE HAVE TO SET $dbPayment->{department_id} in order to process
  838. if ($request->{department}) {
  839. $request->{department} =~ /^(\d+)--*/;
  840. $Payment->{department_id} = $1;
  841. }
  842. #
  843. # We want to set a gl_description,
  844. # since we are using two tables there is no need to use doubled information,
  845. # we could specify this gl is the result of a payment movement...
  846. #
  847. $Payment->{gl_description} = $locale->text('This gl movement, is the result of a payment transaction');
  848. #
  849. # Im not sure what this is for... gotta comment this later
  850. $Payment->{approved} = 'true';
  851. #
  852. # We have to setup a lot of things before we can process the payment
  853. # they are related to payment_post sql function, so if you have any doubts
  854. # look there.
  855. #-------------------------------------------------------------------------
  856. #
  857. # Variable definition
  858. #
  859. # We use the prefix op to refer to the overpayment variables.
  860. my $unhandled_overpayment = 0; # This variable might be fuzzy, we are using it to handle invalid data
  861. # i.e. a user set an overpayment qty inside an invoice.
  862. my @array_options;
  863. my @amount;
  864. my @discount;
  865. my @cash_account_id;
  866. my @memo;
  867. my @source;
  868. my @transaction_id;
  869. my @op_amount;
  870. my @op_cash_account_id;
  871. my @op_source;
  872. my @op_memo;
  873. my @op_account_id;
  874. #
  875. # We need the invoices in order to process the income data, this is done this way
  876. # since the data we have isn't indexed in any way.
  877. #
  878. # Ok, we want to use the disccount information in order to do some accounting movements,
  879. # we will process it with the same logic for a regular payment, and see where does this leave us.
  880. @array_options = $Payment->get_entity_credit_account();# We need to know the disccount account
  881. my $discount_account_id = $array_options[0]->{discount};
  882. @array_options = $Payment->get_open_invoices();
  883. for my $ref (0 .. $#array_options) {
  884. if ( !$request->{"checkbox_$array_options[$ref]->{invoice_id}"}) {
  885. # First i have to determine if discounts will apply
  886. # we will assume that a discount should apply only
  887. # if this is the last payment of an invoice
  888. my $temporary_discount = 0;
  889. if (($request->{"optional_discount_$array_options[$ref]->{invoice_id}"})&&("$array_options[$ref]->{due_fx}" <= $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} + $array_options[$ref]->{discount_fx})) {
  890. $temporary_discount = $array_options[$ref]->{discount_fx};
  891. }
  892. #
  893. # The prefix cash is to set the movements of the cash accounts,
  894. # same names are used for ap/ar accounts w/o the cash prefix.
  895. #
  896. if ( "$array_options[$ref]->{due_fx}" < $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} ) {
  897. # We need to store all the overpayments so we can use it on a new payment2 screen
  898. $unhandled_overpayment = $request->round_amount($unhandled_overpayment + $request->{"topay_fx_$array_options[$ref]->{invoice_id}"} + $temporary_discount - $array_options[$ref]->{amount}) ;
  899. }
  900. if ($request->{"optional_discount_$array_options[$ref]->{invoice_id}"}) {
  901. push @amount, $array_options[$ref]->{discount_fx};
  902. push @cash_account_id, $discount_account_id;
  903. push @source, $locale->text('Applied discount');
  904. push @transaction_id, $array_options[$ref]->{invoice_id};
  905. }
  906. push @amount, $request->{"topay_fx_$array_options[$ref]->{invoice_id}"}; # We'll use this for both cash and ap/ar accounts
  907. push @cash_account_id, $request->{"optional_pay_$array_options[$ref]->{invoice_id}"} ? $request->{"account_$array_options[$ref]->{invoice_id}"} : $request->{account};
  908. push @source, $request->{"optional_pay_$array_options[$ref]"} ?
  909. $request->{"source_$array_options[$ref]->{invoice_id}"}.' '.$request->{"source_text_$array_options[$ref]->{invoice_id}"}
  910. : $request->{source}.' '.$request->{source_value}; # We'll use this for both source and ap/ar accounts
  911. push @memo, $request->{"memo_invoice_$array_options[$ref]->{invoice_id}"};
  912. push @transaction_id, $array_options[$ref]->{invoice_id};
  913. }
  914. }
  915. # Check if there is an unhandled overpayment and run payment2 as needed
  916. if ($unhandled_overpayment) {
  917. &payment2($request);
  918. return 0;
  919. }
  920. #
  921. # Now we need the overpayment information.
  922. #
  923. # We will use the prefix op to indicate it is an overpayment information.
  924. #
  925. # note: I love the for's C-like syntax.
  926. for (my $i=1 ; $i <= $request->{overpayment_qty}; $i++) {
  927. if (!$request->{"overpayment_checkbox_$i"}) { # Is overpayment marked as deleted ?
  928. if ( $request->{"overpayment_topay_$i"} ) { # Is this overpayment an used field?
  929. # Now we split the account selected options, using the namespace the if statement
  930. # provides for us.
  931. $request->{"overpayment_account_$i"} =~ /^(\d+)--*/;
  932. my $id = $1;
  933. $request->{"overpayment_cash_account_$i"} =~ /^(\d+)--*/;
  934. my $cashid = $1;
  935. push @op_amount, $request->{"overpayment_topay_$i"};
  936. push @op_cash_account_id, $cashid;
  937. push @op_source, $request->{"overpayment_source1_$i"}.' '.$request->{"overpayment_source2_$i"};
  938. push @op_memo, $request->{"overpayment_memo_$i"};
  939. push @op_account_id, $id;
  940. }
  941. }
  942. }
  943. # Finally we store all the data inside the LedgerSMB::DBObject::Payment object.
  944. $Payment->{cash_account_id} = $Payment->_db_array_scalars(@cash_account_id);
  945. $Payment->{amount} = $Payment->_db_array_scalars(@amount);
  946. $Payment->{source} = $Payment->_db_array_scalars(@source);
  947. $Payment->{memo} = $Payment->_db_array_scalars(@memo);
  948. $Payment->{transaction_id} = $Payment->_db_array_scalars(@transaction_id);
  949. $Payment->{op_amount} = $Payment->_db_array_scalars(@op_amount);
  950. $Payment->{op_cash_account_id} = $Payment->_db_array_scalars(@op_cash_account_id);
  951. $Payment->{op_source} = $Payment->_db_array_scalars(@op_source);
  952. $Payment->{op_memo} = $Payment->_db_array_scalars(@op_memo);
  953. $Payment->{op_account_id} = $Payment->_db_array_scalars(@op_account_id);
  954. # Ok, passing the control to postgresql and hoping for the best...
  955. $Payment->post_payment();
  956. if ($request->{continue_to_calling_sub}){ return $Payment->{payment_id} ;}
  957. else {
  958. # Our work here is done, ask for more payments.
  959. &payment($request);
  960. }
  961. }
  962. =pod
  963. =item print_payment
  964. This sub will print the payment on the selected media, it needs to
  965. receive the $Payment object with all this information.
  966. =back
  967. =cut
  968. sub print_payment {
  969. my ($Payment) = @_;
  970. my $locale = $Payment->{_locale};
  971. $Payment->gather_printable_info();
  972. my $header = @{$Payment->{header_info}}[0];
  973. my @rows = @{$Payment->{line_info}};
  974. ###############################################################################
  975. # FIRST CODE SECTION
  976. #
  977. # THE FOLLOWING LINES OF CODE ADD SOME EXTRA PROCESSING TO THE DATA THAT
  978. # WILL BE AVAILIBLE ON THE UI,
  979. # PLEASE FEEL FREE TO ADD EXTRA LINES IF YOU NEED IT (AND KNOW WHAT YOU ARE DOING).
  980. ###############################################################################
  981. # First we need to solve some ugly behaviour in the template system
  982. $header->{amount} = abs("$header->{amount}");
  983. # The next code will enable number to text conversion
  984. $Payment->init();
  985. $header->{amount2text} = $Payment->num2text($header->{amount});
  986. ############################################################################
  987. # $Payment->{format_amount} = sub {return $Payment->format_amount(@_); };
  988. # IF YOU NEED MORE INFORMATION ON THE HEADER AND ROWS ITEMS CHECK SQL FUNCTIONS
  989. # payment_gather_header_info AND payment_gather_line_info
  990. my $select = {
  991. header => $header,
  992. rows => \@rows
  993. };
  994. my $template = LedgerSMB::Template->new(
  995. user => $Payment->{_user},
  996. locale => $Payment->{_locale},
  997. path => "templates/test/",
  998. template => 'printPayment',
  999. format => 'HTML' );
  1000. eval {$template->render($select) };
  1001. if ($@) { $Payment->error("$@"); } # PRINT ERRORS ON THE UI
  1002. }
  1003. =pod
  1004. =item post_and_print_payment
  1005. This is simply a shortcut between post_payment and print_payment methods, please refer
  1006. to these functions
  1007. =back
  1008. =cut
  1009. sub post_and_print_payment {
  1010. my ($request) = @_;
  1011. $request->{continue_to_calling_sub} = 1;
  1012. $request->{payment_id} = &post_payment($request);
  1013. my $locale = $request->{_locale};
  1014. my $Payment = LedgerSMB::DBObject::Payment->new({'base' => $request});
  1015. &print_payment($Payment);
  1016. }
  1017. eval { do "scripts/custom/payment.pl"};
  1018. 1;