summaryrefslogtreecommitdiff
path: root/LedgerSMB/DBObject/Payment.pm
blob: 655266098d427b6d2361cbbb4158bf3ad7f9d5b9 (plain)
  1. =head1 NAME
  2. LedgerSMB::Payment: Payment Handling Back-end Routines for LedgerSMB
  3. =head1 SYNOPSIS
  4. Provides the functions for generating the data structures payments made in
  5. LedgerSMB. This module currently handles only basic payment logic, and does
  6. handle overpayment logic, though these features will be moved into this module
  7. in the near future.
  8. =head1 COPYRIGHT
  9. Copyright (c) 2007 The LedgerSMB Core Team. Licensed under the GNU General
  10. Public License version 2 or at your option any later version. Please see the
  11. included COPYRIGHT and LICENSE files for more information.
  12. =cut
  13. package LedgerSMB::DBObject::Payment;
  14. use LedgerSMB::Num2text;
  15. use base qw(LedgerSMB::DBObject);
  16. use strict;
  17. use Math::BigFloat lib => 'GMP';
  18. use Data::Dumper;
  19. our $VERSION = '0.1.0';
  20. =head1 METHODS
  21. =over
  22. =item LedgerSMB::DBObject::Payment->new()
  23. Inherited from LedgerSMB::DBObject. Please see that documnetation for details.
  24. =item $payment->get_open_accounts()
  25. This function returns a list of open accounts depending on the
  26. $payment->{account_class} property. If this property is 1, it returns a list
  27. of vendor accounts, for 2, a list of customer accounts are returned.
  28. The returned list of hashrefs is stored in the $payment->{accounts} property.
  29. Each hashref has the following keys: id (entity id), name, and entity_class.
  30. An account is considered open if there are outstanding, unpaid invoices
  31. attached to it. Customer/vendor payment threshold is not considered for this
  32. calculation.
  33. =back
  34. =cut
  35. sub __validate__ {
  36. my ($self) = shift @_;
  37. # If the account class is not set, we don't know if it is a payment or a
  38. # receipt. --CT
  39. if (!$self->{account_class}) {
  40. $self->error("account_class must be set")
  41. };
  42. # We should try to re-engineer this so that we don't have to include SQL in
  43. # this file. --CT
  44. ($self->{current_date}) = $self->{dbh}->selectrow_array('select current_date');
  45. }
  46. sub text_amount {
  47. use LedgerSMB::Num2text;
  48. my ($self, $value) = @_;
  49. $self->{locale} = $self->{_locale};
  50. $self->init();
  51. return $self->num2text($value);
  52. }
  53. sub get_metadata {
  54. my ($self) = @_;
  55. $self->list_open_projects();
  56. @{$self->{departments}} = $self->exec_method(funcname => 'department_list');
  57. $self->get_open_currencies();
  58. $self->{currencies} = [];
  59. for my $c (@{$self->{openCurrencies}}){
  60. push @{$self->{currencies}}, $c->{payments_get_open_currencies};
  61. }
  62. @{$self->{businesses}} = $self->exec_method(
  63. funcname => 'business_type__list'
  64. );
  65. @{$self->{debt_accounts}} = $self->exec_method(
  66. funcname => 'chart_get_ar_ap');
  67. @{$self->{cash_accounts}} = $self->exec_method(
  68. funcname => 'chart_list_cash');
  69. for my $ref(@{$self->{cash_accounts}}){
  70. $ref->{text} = "$ref->{accno}--$ref->{description}";
  71. }
  72. if ($self->{batch_id} && !defined $self->{batch_date}){
  73. my ($ref) = $self->exec_method(funcname => 'voucher_get_batch');
  74. $self->{batch_date} = $ref->{default_date};
  75. }
  76. }
  77. sub search {
  78. my ($self) = @_;
  79. if ($self->{meta_number} && !$self->{credit_id}){
  80. my ($ref) = $self->exec_method(
  81. funcname => 'entity_credit_get_id_by_meta_number'
  82. );
  83. my @keys = keys %$ref;
  84. my $key = shift @keys;
  85. $self->{credit_id} = $ref->{$key};
  86. }
  87. @{$self->{search_results}} = $self->exec_method(
  88. funcname => 'payment__search'
  89. );
  90. return @{$self->{search_results}};
  91. }
  92. sub get_open_accounts {
  93. my ($self) = @_;
  94. @{$self->{accounts}} =
  95. $self->exec_method(funcname => 'payment_get_open_accounts');
  96. return @{$self->{accounts}};
  97. }
  98. sub get_entity_credit_account{
  99. my ($self) = @_;
  100. @{$self->{entity_accounts}} =
  101. $self->exec_method(funcname => 'payment_get_entity_accounts');
  102. return @{$self->{entity_accounts}};
  103. }
  104. =over
  105. =item $payment->get_all_accounts()
  106. This function returns a list of open or closed accounts depending on the
  107. $payment->{account_class} property. If this property is 1, it returns a list
  108. of vendor accounts, for 2, a list of customer accounts are returned.
  109. The returned list of hashrefs is stored in the $payment->{accounts} property.
  110. Each hashref has the following keys: id (entity id), name, and entity_class.
  111. =back
  112. =cut
  113. sub get_all_accounts {
  114. my ($self) = @_;
  115. @{$self->{accounts}} =
  116. $self->exec_method(funcname => 'payment_get_all_accounts');
  117. return @{$self->{accounts}};
  118. }
  119. =over
  120. =item $payment->reverse()
  121. This function reverses a payment. A payment is defined as one source
  122. ($payment->{source}) to one cash account ($payment->{cash_accno}) to one date
  123. ($payment->{date_paid}) to one vendor/customer ($payment->{credit_id},
  124. $payment->{account_class}). This reverses the entries with that source.
  125. =back
  126. =cut
  127. sub reverse {
  128. my ($self) = @_;
  129. $self->exec_method(funcname => 'payment__reverse');
  130. return $self->{dbh}->commit;
  131. }
  132. =over
  133. =item $payment->get_open_invoices()
  134. This function returns a list of open invoices depending on the
  135. $payment->{account_class}, $payment->{entity_id}, and $payment->{curr}
  136. properties. Account classes follow the conventions above. This list is hence
  137. specific to a customer or vendor and currency as well.
  138. The returned list of hashrefs is stored in the $payment->{open_invoices}
  139. property. Each hashref has the following keys: id (entity id), name, and
  140. entity_class.
  141. =back
  142. =cut
  143. sub get_open_invoices {
  144. my ($self) = @_;
  145. @{$self->{open_invoices}} =
  146. $self->exec_method(funcname => 'payment_get_open_invoices');
  147. return @{$self->{open_invoices}};
  148. }
  149. =over
  150. =item $payment->get_all_contact_invoices()
  151. This function returns a list of open accounts depending on the
  152. $payment->{account_class} property. If this property is 1, it returns a list
  153. of vendor accounts, for 2, a list of customer accounts are returned. Attached
  154. to each account is a list of open invoices. The data structure is somewhat
  155. complex.
  156. Each item in the list has the following keys: contact_id, contact_name, \
  157. account_number, total_due, and invoices.
  158. The invoices entry is a reference to an array of hashrefs. Each of these
  159. hashrefs has the following keys: invoice_id, invnumber, invoice_date, amount,
  160. discount, and due.
  161. These are filtered based on the (required) properties:
  162. $payment->{account_class}, $payment->{business_type}, $payment->{date_from},
  163. $payment->{date_to}, and $payment->{ar_ap_accno}.
  164. The $payment->{ar_ap_accno} property is used to filter out by AR or AP account.
  165. The following can also be optionally passed: $payment->{batch_id}. If this is
  166. patched, vouchers in the current batch will be picked up as well.
  167. The returned list of hashrefs is stored in the $payment->{contact} property.
  168. Each hashref has the following keys: id (entity id), name, and entity_class.
  169. =back
  170. =cut
  171. sub get_all_contact_invoices {
  172. my ($self) = @_;
  173. @{$self->{contacts}} =
  174. $self->exec_method(funcname => 'payment_get_all_contact_invoices');
  175. # When arrays of complex types are supported by all versions of Postgres
  176. # that this application supports, we should look at doing type conversions
  177. # in DBObject so this sort of logic is unncessary. -- CT
  178. for my $contact (@{$self->{contacts}}){
  179. my @invoices = $self->parse_array($contact->{invoices});
  180. my $processed_invoices = [];
  181. for my $invoice (@invoices){
  182. my $new_invoice = {};
  183. for (qw(invoice_id invnumber invoice_date amount discount due)){
  184. $new_invoice->{$_} = shift @$invoice;
  185. if ($_ =~ /^(amount|discount|due)$/){
  186. $new_invoice->{$_} =
  187. Math::BigFloat->new($new_invoice->{$_});
  188. }
  189. }
  190. push(@$processed_invoices, $new_invoice);
  191. }
  192. $contact->{invoice} = sort { $a->{invoice_date} cmp $b->{invoice_date} } @{ $processed_invoices };
  193. $contact->{invoice} = $processed_invoices;
  194. }
  195. return @{$self->{contacts}};
  196. }
  197. =over
  198. =item list_open_projects
  199. This method gets the current date attribute, and provides a list of open
  200. projects. The list is attached to $self->{projects} and returned.
  201. =back
  202. =cut
  203. sub list_open_projects {
  204. my ($self) = @_;
  205. @{$self->{projects}} = $self->call_procedure(
  206. procname => 'project_list_open', args => [$self->{current_date}]
  207. );
  208. return @{$self->{projects}};
  209. }
  210. =over
  211. =item list_departments
  212. This method gets the type of document as a parameter, and provides a list of departments
  213. of the required type.
  214. The list is attached to $self->{departments} and returned.
  215. =back
  216. =cut
  217. sub list_departments {
  218. my ($self) = shift @_;
  219. my @args = @_;
  220. @{$self->{departments}} = $self->call_procedure(
  221. procname => 'department_list',
  222. args => \@args
  223. );
  224. return @{$self->{departments}};
  225. }
  226. =over
  227. =item list_open_vc
  228. This method gets the type of vc (vendor or customer) as a parameter, and provides a list of departments
  229. of the required type.
  230. The list is attached to $self->{departments} and returned.
  231. =back
  232. =cut
  233. sub list_departments {
  234. my ($self) = shift @_;
  235. my @args = @_;
  236. @{$self->{departments}} = $self->call_procedure(
  237. procname => 'department_list',
  238. args => \@args
  239. );
  240. return @{$self->{departments}};
  241. }
  242. =over
  243. =item get_open_currencies
  244. This method gets a list of the open currencies inside the database, it requires that
  245. $self->{account_class} (must be 1 or 2) exist to work.
  246. =back
  247. =cut
  248. sub get_open_currencies {
  249. my ($self) = shift @_;
  250. @{$self->{openCurrencies}} = $self->exec_method( funcname => 'payments_get_open_currencies');
  251. return @{$self->{openCurrencies}};
  252. }
  253. =over
  254. =item list_accounting
  255. This method lists all accounts that match the role specified in account_class property and
  256. are availible to store the payment or receipts.
  257. =back
  258. =cut
  259. sub list_accounting {
  260. my ($self) = @_;
  261. @{$self->{pay_accounts}} = $self->exec_method( funcname => 'chart_list_cash');
  262. return @{$self->{pay_accounts}};
  263. }
  264. =item list_overpayment_accounting
  265. This method lists all accounts that match the role specified in account_class property and
  266. are availible to store an overpayment / advanced payment / pre-payment.
  267. =back
  268. =cut
  269. sub list_overpayment_accounting {
  270. my ($self) = @_;
  271. @{$self->{overpayment_accounts}} = $self->exec_method( funcname => 'chart_list_overpayment');
  272. return @{$self->{overpayment_accounts}};
  273. }
  274. =item get_sources
  275. This method builds all the possible sources of money,
  276. in the future it will look inside the DB.
  277. =back
  278. =cut
  279. sub get_sources {
  280. my ($self, $locale) = @_;
  281. @{$self->{cash_sources}} = ($locale->text('cash'),
  282. $locale->text('check'),
  283. $locale->text('deposit'),
  284. $locale->text('other'));
  285. return @{$self->{cash_sources}};
  286. }
  287. =item get_exchange_rate(currency, date)
  288. This method gets the exchange rate for the specified currency and date
  289. =cut
  290. sub get_exchange_rate {
  291. my ($self) = shift @_;
  292. ($self->{currency}, $self->{date}) = @_;
  293. ($self->{exchangerate}) = $self->exec_method(funcname => 'currency_get_exchangerate');
  294. return $self->{exchangerate}->{currency_get_exchangerate};
  295. }
  296. =item get_default_currency
  297. This method gets the default currency
  298. =back
  299. =cut
  300. sub get_default_currency {
  301. my ($self) = shift @_;
  302. ($self->{default_currency}) = $self->call_procedure(procname => 'defaults_get_defaultcurrency');
  303. return $self->{default_currency}->{defaults_get_defaultcurrency};
  304. }
  305. =item get_current_date
  306. This method returns the system's current date
  307. =cut
  308. sub get_current_date {
  309. my ($self) = shift @_;
  310. return $self->{current_date};
  311. }
  312. =item get_vc_info
  313. This method returns the contact informatino for a customer or vendor according to
  314. $self->{account_class}
  315. =cut
  316. sub get_vc_info {
  317. my ($self) = @_;
  318. @{$self->{vendor_customer_info}} = $self->exec_method(funcname => 'payment_get_vc_info');
  319. return @{$self->{vendor_customer_info}};
  320. }
  321. =item get_payment_detail_data
  322. This method sets appropriate project, department, etc. fields.
  323. =cut
  324. sub get_payment_detail_data {
  325. my ($self) = @_;
  326. $self->get_metadata();
  327. my $source_inc;
  328. my $source_src;
  329. if (defined ($self->{source_start})) {
  330. $self->{source_start} =~ /(\d*)\D*$/;
  331. $source_src = $1;
  332. if ($source_src) {
  333. $source_inc = $source_src;
  334. } else {
  335. $source_inc = 0;
  336. }
  337. }
  338. my $source_length = length($source_inc);
  339. @{$self->{contact_invoices}} = $self->exec_method(
  340. funcname => 'payment_get_all_contact_invoices');
  341. for my $inv (@{$self->{contact_invoices}}) {
  342. if (defined $self->{source_start}) {
  343. my $source = $self->{source_start};
  344. if (length($source_inc) < $source_length) {
  345. $source_inc = sprintf('%0*s', $source_length, $source_inc);
  346. }
  347. $source =~ s/$source_src(\D*)$/$source_inc$1/;
  348. ++ $source_inc;
  349. $inv->{source} = $source;
  350. }
  351. my $tmp_invoices = $inv->{invoices};
  352. $inv->{invoices} = [];
  353. @{$inv->{invoices}} = $self->_parse_array($tmp_invoices);
  354. @{$inv->{invoices}} = sort { $a->[2] cmp $b->[2] } @{ $inv->{invoices} };
  355. for my $invoice (@{$inv->{invoices}}){
  356. $invoice->[6] = Math::BigFloat->new($invoice->[6]);
  357. $invoice->[3] = Math::BigFloat->new($invoice->[3]);
  358. $invoice->[4] = Math::BigFloat->new($invoice->[4]);
  359. }
  360. }
  361. $self->{dbh}->commit; # Commit locks
  362. }
  363. sub post_bulk {
  364. my ($self) = @_;
  365. my $total_count = 0;
  366. my ($ref) = $self->call_procedure(
  367. procname => 'setting_get',
  368. args => ['queue_payments'],
  369. );
  370. my $queue_payments = $ref->{setting_get};
  371. if ($queue_payments){
  372. my ($job_ref) = $self->exec_method(
  373. funcname => 'job__create'
  374. );
  375. $self->{job_id} = $job_ref->{job__create};
  376. ($self->{job}) = $self->exec_method(
  377. funcname => 'job__status'
  378. );
  379. }
  380. $self->{payment_date} = $self->{datepaid};
  381. for my $contact_row (1 .. $self->{contact_count}){
  382. my $contact_id = $self->{"contact_$contact_row"};
  383. next if (!$self->{"id_$contact_id"});
  384. my $invoice_array = "{}"; # Pg Array
  385. for my $invoice_row (1 .. $self->{"invoice_count_$contact_id"}){
  386. my $invoice_id = $self->{"invoice_${contact_id}_${invoice_row}"};
  387. my $pay_amount = ($self->{"paid_$contact_id"} eq 'all' )
  388. ? $self->{"net_$invoice_id"}
  389. : $self->{"payment_$invoice_id"};
  390. next if ! $pay_amount;
  391. $pay_amount = $pay_amount * 1;
  392. my $invoice_subarray = "{$invoice_id,$pay_amount}";
  393. if ($invoice_subarray !~ /^\{\d+\,\-?\d*\.?\d+\}$/){
  394. $self->error("Invalid subarray: $invoice_subarray");
  395. }
  396. $invoice_subarray =~ s/[^0123456789{},.-]//;
  397. if ($invoice_array eq '{}'){ # Omit comma
  398. $invoice_array = "{$invoice_subarray}";
  399. } else {
  400. $invoice_array =~ s/\}$/,$invoice_subarray\}/;
  401. }
  402. }
  403. $self->{transactions} = $invoice_array;
  404. $self->{source} = $self->{"source_$contact_id"};
  405. if ($queue_payments){
  406. $self->{batch_class} = 3;
  407. $self->exec_method(
  408. funcname => 'payment_bulk_queue'
  409. );
  410. } else {
  411. $self->exec_method(funcname => 'payment_bulk_post');
  412. }
  413. }
  414. $self->{queue_payments} = $queue_payments;
  415. return $self->{dbh}->commit;
  416. }
  417. sub check_job {
  418. my ($self) = @_;
  419. ($self->{job}) = $self->exec_method(funcname => 'job__status');
  420. }
  421. =item post_payment
  422. This method uses payment_post to store a payment (not a bulk payment) on the database.
  423. =cut
  424. sub post_payment {
  425. my ($self) = @_;
  426. # We have to check if it was a fx_payment
  427. $self->{currency} = $self->{curr};
  428. if ("$self->{currency}" ne $self->get_default_currency()) {
  429. # First we have to check for an exchangerate on this date
  430. my $db_exchangerate = $self->get_exchange_rate($self->{curr},$self->{datepaid});
  431. if (!$db_exchangerate) {
  432. # We have to set the exchangerate
  433. $self->call_procedure(procname => 'payments_set_exchangerate', args => ["$self->{account_class}", "$self->{exchangerate}" ,"$self->{curr}", "$self->{datepaid}"]);
  434. }
  435. elsif ($db_exchangerate != $self->{exchangerate} )
  436. {
  437. # Something went wrong
  438. $self->error("Exchange rate inconsistency with database, please try again")
  439. }
  440. }
  441. my @TMParray = $self->exec_method(funcname => 'payment_post');
  442. $self->{dbh}->commit();
  443. $self->{payment_id} = $TMParray[0]->{payment_post};
  444. return $self->{payment_id};
  445. }
  446. =item gather_printable_info
  447. This method retrieves all the payment related info needed to build a
  448. document and print it. IT IS NECESSARY TO ALREADY HAVE payment_id on $self
  449. =cut
  450. sub gather_printable_info {
  451. my ($self) = @_;
  452. @{$self->{header_info}} = $self->exec_method(funcname => 'payment_gather_header_info');
  453. @{$self->{line_info}} = $self->exec_method(funcname => 'payment_gather_line_info');
  454. }
  455. 1;