summaryrefslogtreecommitdiff
path: root/LedgerSMB/IR.pm
blob: 76fef000c83d498f8f0dd814bc83c7cd9baf1d58 (plain)
  1. #=====================================================================
  2. # LedgerSMB
  3. # Small Medium Business Accounting software
  4. # http://www.ledgersmb.org/
  5. #
  6. # Copyright (C) 2006
  7. # This work contains copyrighted information from a number of sources all used
  8. # with permission.
  9. #
  10. # This file contains source code included with or based on SQL-Ledger which
  11. # is Copyright Dieter Simader and DWS Systems Inc. 2000-2005 and licensed
  12. # under the GNU General Public License version 2 or, at your option, any later
  13. # version. For a full list including contact information of contributors,
  14. # maintainers, and copyright holders, see the CONTRIBUTORS file.
  15. #
  16. # Original Copyright Notice from SQL-Ledger 2.6.17 (before the fork):
  17. # Copyright (C) 2000
  18. #
  19. # Author: DWS Systems Inc.
  20. # Web: http://www.sql-ledger.org
  21. #
  22. # Contributors: Jim Rawlings <jim@your-dba.com>
  23. #
  24. #======================================================================
  25. #
  26. # This file has undergone whitespace cleanup.
  27. #
  28. #======================================================================
  29. #
  30. # Inventory received module
  31. #
  32. #======================================================================
  33. package IR;
  34. use LedgerSMB::Tax;
  35. use LedgerSMB::PriceMatrix;
  36. use LedgerSMB::Sysconfig;
  37. sub post_invoice {
  38. my ($self, $myconfig, $form) = @_;
  39. my $dbh = $form->{dbh};
  40. my $query;
  41. my $sth;
  42. my $ref;
  43. my $null;
  44. my $project_id;
  45. my $exchangerate = 0;
  46. my $allocated;
  47. my $taxrate;
  48. my $taxamount;
  49. my $diff = 0;
  50. my $item;
  51. my $invoice_id;
  52. my $keepcleared;
  53. ($null, $form->{employee_id}) = split /--/, $form->{employee};
  54. unless ($form->{employee_id}) {
  55. ($form->{employee}, $form->{employee_id})
  56. = $form->get_employee($dbh);
  57. }
  58. ($null, $form->{department_id}) = split(/--/, $form->{department});
  59. $form->{department_id} *= 1;
  60. $query = qq|SELECT fxgain_accno_id, fxloss_accno_id FROM defaults d|;
  61. my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query);
  62. $query = qq|
  63. SELECT inventory_accno_id, income_accno_id, expense_accno_id
  64. FROM parts
  65. WHERE id = ?|;
  66. my $pth = $dbh->prepare($query) || $form->dberror($query);
  67. my %updparts = ();
  68. if ($form->{id}) {
  69. my $sth;
  70. $keepcleared = 1;
  71. $query = qq|SELECT id FROM ap WHERE id = ?|;
  72. $sth = $dbh->prepare($query);
  73. $sth->execute($form->{id});
  74. if ($sth->fetchrow_array($query)) {
  75. $query = qq|
  76. SELECT p.id, p.inventory_accno_id,
  77. p.income_accno_id
  78. FROM invoice i
  79. JOIN parts p ON (p.id = i.parts_id)
  80. WHERE i.trans_id = ?|;
  81. $sth = $dbh->prepare($query);
  82. $sth->execute($form->{id}) || $form->dberror($query);
  83. while ($ref = $sth->fetchrow_hashref) {
  84. if ($ref->{inventory_accno_id}
  85. && $ref->{income_accno_id}) {
  86. $updparts{$ref->{id}} = 1;
  87. }
  88. }
  89. $sth->finish;
  90. &reverse_invoice($dbh, $form);
  91. } else {
  92. $query = qq|INSERT INTO ap (id) VALUES (?)|;
  93. $sth = $dbh->prepare($query);
  94. $sth->execute($form->{id}) || $form->dberror($query);
  95. }
  96. }
  97. my $uid = localtime;
  98. $uid .= "$$";
  99. if (! $form->{id}) {
  100. $query = qq|
  101. INSERT INTO ap (invnumber, employee_id)
  102. VALUES ('$uid', (SELECT id FROM employee
  103. WHERE login = ?))|;
  104. $sth = $dbh->prepare($query);
  105. $sth->execute($form->{login}) || $form->dberror($query);
  106. $query = qq|SELECT id FROM ap WHERE invnumber = '$uid'|;
  107. $sth = $dbh->prepare($query);
  108. $sth->execute || $form->dberror($query);
  109. ($form->{id}) = $sth->fetchrow_array;
  110. $sth->finish;
  111. }
  112. my $amount;
  113. my $grossamount;
  114. my $allocated;
  115. my $invamount = 0;
  116. my $invnetamount = 0;
  117. if ($form->{currency} eq $form->{defaultcurrency}) {
  118. $form->{exchangerate} = 1;
  119. } else {
  120. $exchangerate =
  121. $form->check_exchangerate(
  122. $myconfig, $form->{currency},
  123. $form->{transdate}, 'sell');
  124. }
  125. $form->{exchangerate} =
  126. ($exchangerate)
  127. ? $exchangerate
  128. : $form->parse_amount($myconfig, $form->{exchangerate});
  129. for my $i (1 .. $form->{rowcount}) {
  130. $form->{"qty_$i"} =
  131. $form->parse_amount($myconfig, $form->{"qty_$i"});
  132. if ($form->{"qty_$i"}) {
  133. $pth->execute($form->{"id_$i"});
  134. $ref = $pth->fetchrow_hashref(NAME_lc);
  135. for (keys %$ref) {
  136. $form->{"${_}_$i"} = $ref->{$_};
  137. }
  138. $pth->finish;
  139. # project
  140. $project_id = 'NULL';
  141. if ($form->{"projectnumber_$i"} ne "") {
  142. ($null, $project_id) =
  143. split /--/, $form->{"projectnumber_$i"};
  144. }
  145. # undo discount formatting
  146. $form->{"discount_$i"} =
  147. $form->parse_amount(
  148. $myconfig,
  149. $form->{"discount_$i"}) / 100;
  150. # keep entered selling price
  151. my $fxsellprice =
  152. $form->parse_amount(
  153. $myconfig, $form->{"sellprice_$i"});
  154. my ($dec) = ($fxsellprice =~ /\.(\d+)/);
  155. $dec = length $dec;
  156. my $decimalplaces = ($dec > 2) ? $dec : 2;
  157. # deduct discount
  158. $form->{"sellprice_$i"} =
  159. $fxsellprice - $form->round_amount(
  160. $fxsellprice * $form->{"discount_$i"},
  161. $decimalplaces);
  162. # linetotal
  163. my $fxlinetotal = $form->round_amount(
  164. $form->{"sellprice_$i"} * $form->{"qty_$i"}, 2);
  165. $amount = $fxlinetotal * $form->{exchangerate};
  166. my $linetotal = $form->round_amount($amount, 2);
  167. $fxdiff += $amount - $linetotal;
  168. @taxaccounts = Tax::init_taxes(
  169. $form, $form->{"taxaccounts_$i"});
  170. $tax = Math::BigFloat->bzero();
  171. $fxtax = Math::BigFloat->bzero();
  172. if ($form->{taxincluded}) {
  173. $tax += $amount = Tax::calculate_taxes(
  174. \@taxaccounts, $form, $linetotal, 1);
  175. $form->{"sellprice_$i"}
  176. -= $amount / $form{"qty_$i"};
  177. } else {
  178. $tax += $amount = Tax::calculate_taxes(
  179. \@taxaccounts, $form, $linetotal, 0);
  180. $fxtax += Tax::calculate_taxes(
  181. \@taxaccounts, $form, $fxlinetotal, 0);
  182. }
  183. for (@taxaccounts) {
  184. $form->{acc_trans}{$form->{id}}{$_->account}{amount} += $_->value;
  185. }
  186. $grossamount = $form->round_amount($linetotal, 2);
  187. if ($form->{taxincluded}) {
  188. $amount = $form->round_amount($tax, 2);
  189. $linetotal -= $form->round_amount(
  190. $tax - $diff, 2);
  191. $diff = ($amount - $tax);
  192. }
  193. $amount = $form->round_amount($linetotal, 2);
  194. $allocated = 0;
  195. # adjust and round sellprice
  196. $form->{"sellprice_$i"} = $form->round_amount(
  197. $form->{"sellprice_$i"} * $form->{exchangerate},
  198. $decimalplaces);
  199. # save detail record in invoice table
  200. $query = qq|
  201. INSERT INTO invoice (description)
  202. VALUES ('$uid')|;
  203. $dbh->do($query) || $form->dberror($query);
  204. $query = qq|
  205. SELECT id FROM invoice
  206. WHERE description = '$uid'|;
  207. ($invoice_id) = $dbh->selectrow_array($query);
  208. $query = qq|
  209. UPDATE invoice
  210. SET trans_id = ?,
  211. parts_id = ?,
  212. description = ?,
  213. qty = ? * -1,
  214. sellprice = ?,
  215. fxsellprice = ?,
  216. discount = ?,
  217. allocated = ?,
  218. unit = ?,
  219. deliverydate = ?,
  220. project_id = ?,
  221. serialnumber = ?,
  222. notes = ?
  223. WHERE id = ?|;
  224. $sth = $dbh->prepare($query);
  225. $sth->execute(
  226. $form->{id}, $form->{"id_$i"},
  227. $form->{"description_$i"}, $form->{"qty_$i"},
  228. $form->{"sellprice_$i"}, $fxsellprice,
  229. $form->{"discount_$i"}, $allocated,
  230. $form->{"unit_$i"}, $form->{"deliverydate_$i"},
  231. $project_id, $form->{"serialnumber_$i"},
  232. $form->{"notes_$i"}, $invoice_id)
  233. || $form->dberror($query);
  234. if ($form->{"inventory_accno_id_$i"}) {
  235. # add purchase to inventory
  236. push @{ $form->{acc_trans}{lineitems} },
  237. {chart_id =>
  238. $form->{"inventory_accno_id_$i"},
  239. amount => $amount,
  240. fxgrossamount => $fxlinetotal +
  241. $form->round_amount($fxtax, 2),
  242. grossamount => $grossamount,
  243. project_id => $project_id,
  244. invoice_id => $invoice_id };
  245. $updparts{$form->{"id_$i"}} = 1;
  246. # update parts table
  247. $form->update_balance(
  248. $dbh, "parts", "onhand",
  249. qq|id = $form->{"id_$i"}|,
  250. $form->{"qty_$i"})
  251. unless $form->{shipped};
  252. # check if we sold the item
  253. $query = qq|
  254. SELECT i.id, i.qty, i.allocated,
  255. i.trans_id, i.project_id,
  256. p.inventory_accno_id,
  257. p.expense_accno_id, a.transdate
  258. FROM invoice i
  259. JOIN parts p ON (p.id = i.parts_id)
  260. JOIN ar a ON (a.id = i.trans_id)
  261. WHERE i.parts_id = ?
  262. AND (i.qty + i.allocated) > 0
  263. ORDER BY transdate|;
  264. $sth = $dbh->prepare($query);
  265. $sth->execute($form->{"id_$i"})
  266. || $form->dberror($query);
  267. my $totalqty = $form->{"qty_$i"};
  268. while (my $ref =
  269. $sth->fetchrow_hashref(NAME_lc)) {
  270. my $qty = $ref->{qty}
  271. + $ref->{allocated};
  272. if (($qty - $totalqty) > 0) {
  273. $qty = $totalqty;
  274. }
  275. $linetotal = $form->round_amount(
  276. $form->{"sellprice_$i"} * $qty,
  277. 2);
  278. $ref->{project_id} ||= 'NULL';
  279. if ($linetotal) {
  280. $query = qq|
  281. INSERT INTO acc_trans
  282. (trans_id,
  283. chart_id,
  284. amount,
  285. transdate,
  286. project_id,
  287. invoice_id)
  288. VALUES (?, ?, ?, ?,
  289. ?, ?)|;
  290. $sth = $dbh->prepare($query);
  291. $sth->execute(
  292. $ref->{trans_id},
  293. $ref->{inventory_accno_id},
  294. $linetotal,
  295. $ref->{transdate},
  296. $ref->{project_id},
  297. $invoice_id
  298. ) || $form->dberror(
  299. $query);
  300. # add expense
  301. $query = qq|
  302. INSERT INTO acc_trans
  303. (trans_id,
  304. chart_id,
  305. amount,
  306. transdate,
  307. project_id,
  308. invoice_id)
  309. VALUES (?, ?, ?, ?,
  310. ?, ?)|;
  311. $sth = $dbh->prepare($query) ;
  312. $sth->execute(
  313. $ref->{trans_id},
  314. $ref->{expense_accno_id},
  315. $linetotal * -1,
  316. $ref->{transdate},
  317. $ref->{project_id},
  318. $invoice_id
  319. ) || $form->dberror(
  320. $query);
  321. }
  322. # update allocated for sold item
  323. $form->update_balance(
  324. $dbh, "invoice", "allocated",
  325. qq|id = $ref->{id}|, $qty * -1);
  326. $allocated += $qty;
  327. last if (($totalqty -= $qty) <= 0);
  328. }
  329. $sth->finish;
  330. } else {
  331. # add purchase to expense
  332. push @{ $form->{acc_trans}{lineitems} }, {
  333. chart_id =>
  334. $form->{"expense_accno_id_$i"},
  335. amount => $amount,
  336. fxgrossamount => $fxlinetotal
  337. + $form->round_amount(
  338. $fxtax, 2),
  339. grossamount => $grossamount,
  340. project_id => $project_id,
  341. invoice_id => $invoice_id };
  342. }
  343. }
  344. }
  345. $form->{paid} = 0;
  346. for $i (1 .. $form->{paidaccounts}) {
  347. $form->{"paid_$i"} =
  348. $form->parse_amount($myconfig, $form->{"paid_$i"});
  349. $form->{paid} += $form->{"paid_$i"};
  350. $form->{datepaid} = $form->{"datepaid_$i"}
  351. if ($form->{"datepaid_$i"});
  352. }
  353. # add lineitems + tax
  354. $amount = 0;
  355. $grossamount = 0;
  356. $fxgrossamount = 0;
  357. for (@{ $form->{acc_trans}{lineitems} }) {
  358. $amount += $_->{amount};
  359. $grossamount += $_->{grossamount};
  360. $fxgrossamount += $_->{fxgrossamount};
  361. }
  362. $invnetamount = $amount;
  363. $amount = 0;
  364. for (split / /, $form->{taxaccounts}) {
  365. $amount += $form->{acc_trans}{$form->{id}}{$_}{amount}
  366. = $form->round_amount(
  367. $form->{acc_trans}{$form->{id}}{$_}{amount}, 2);
  368. $form->{acc_trans}{$form->{id}}{$_}{amount} *= -1;
  369. }
  370. $invamount = $invnetamount + $amount;
  371. $diff = 0;
  372. if ($form->{taxincluded}) {
  373. $diff = $form->round_amount($grossamount - $invamount, 2);
  374. $invamount += $diff;
  375. }
  376. $fxdiff = $form->round_amount($fxdiff,2);
  377. $invnetamount += $fxdiff;
  378. $invamount += $fxdiff;
  379. if ($form->round_amount($form->{paid} - $fxgrossamount,2) == 0) {
  380. $form->{paid} = $invamount;
  381. } else {
  382. $form->{paid} = $form->round_amount(
  383. $form->{paid} * $form->{exchangerate}, 2);
  384. }
  385. foreach $ref (sort { $b->{amount} <=> $a->{amount} }
  386. @ { $form->{acc_trans}{lineitems} }) {
  387. $amount = $ref->{amount} + $diff + $fxdiff;
  388. $query = qq|
  389. INSERT INTO acc_trans (trans_id, chart_id, amount,
  390. transdate, project_id, invoice_id)
  391. VALUES (?, ?, ? * -1, ?, ?, ?)|;
  392. $sth = $dbh->do($query);
  393. $sth->execute(
  394. $form->{id}, $ref->{chart_id}, $amount,
  395. $form->{transdate}, $ref->{project_id},
  396. $ref->{invoice_id}) || $form->dberror($query);
  397. $diff = 0;
  398. $fxdiff = 0;
  399. }
  400. $form->{payables} = $invamount;
  401. delete $form->{acc_trans}{lineitems};
  402. # update exchangerate
  403. if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
  404. $form->update_exchangerate(
  405. $dbh, $form->{currency}, $form->{transdate}, 0,
  406. $form->{exchangerate});
  407. }
  408. # record payable
  409. if ($form->{payables}) {
  410. ($accno) = split /--/, $form->{AP};
  411. $query = qq|
  412. INSERT INTO acc_trans (trans_id, chart_id, amount,
  413. transdate)
  414. VALUES ?, (SELECT id FROM chart WHERE accno = ?),
  415. ?, ?)|;
  416. $sth = $dbh->prepare($query);
  417. $sth->execute(
  418. $form->{id}, $accno, $form->{payables},
  419. $form->{transdate}
  420. ) || $form->dberror($query);
  421. }
  422. foreach my $trans_id (keys %{$form->{acc_trans}}) {
  423. foreach my $accno (keys %{ $form->{acc_trans}{$trans_id} }) {
  424. $amount = $form->round_amount(
  425. $form->{acc_trans}{$trans_id}{$accno}{amount},
  426. 2);
  427. if ($amount) {
  428. $query = qq|
  429. INSERT INTO acc_trans
  430. (trans_id, chart_id, amount,
  431. transdate)
  432. VALUES (?, (SELECT id FROM chart
  433. WHERE accno = ?),
  434. ?, ?)|;
  435. $sth =$dbh->prepare($query);
  436. $sth->execute(
  437. $trans_id, $accno, $amount,
  438. $form->{transdate}
  439. ) || $form->dberror($query);
  440. }
  441. }
  442. }
  443. # if there is no amount but a payment record payable
  444. if ($invamount == 0) {
  445. $form->{payables} = 1;
  446. }
  447. my $cleared = 0;
  448. # record payments and offsetting AP
  449. for my $i (1 .. $form->{paidaccounts}) {
  450. if ($form->{"paid_$i"}) {
  451. my ($accno) = split /--/, $form->{"AP_paid_$i"};
  452. $form->{"datepaid_$i"} = $form->{transdate}
  453. unless ($form->{"datepaid_$i"});
  454. $form->{datepaid} = $form->{"datepaid_$i"};
  455. $exchangerate = 0;
  456. if ($form->{currency} eq $form->{defaultcurrency}) {
  457. $form->{"exchangerate_$i"} = 1;
  458. } else {
  459. $exchangerate = $form->check_exchangerate(
  460. $myconfig, $form->{currency},
  461. $form->{"datepaid_$i"}, 'sell');
  462. $form->{"exchangerate_$i"} =
  463. ($exchangerate)
  464. ? $exchangerate
  465. : $form->parse_amount(
  466. $myconfig,
  467. $form->{"exchangerate_$i"});
  468. }
  469. # record AP
  470. $amount = ($form->round_amount(
  471. $form->{"paid_$i"} * $form->{exchangerate},
  472. 2)) * -1;
  473. if ($form->{payables}) {
  474. $query = qq|
  475. INSERT INTO acc_trans
  476. (trans_id, chart_id, amount,
  477. transdate)
  478. VALUES (?, (SELECT id FROM chart
  479. WHERE accno = ?),
  480. ?, ?)|;
  481. $sth = $dbh->$dbh->prepare($query);
  482. $sth->execute(
  483. $form->{id}, $form->{AP}, $amount,
  484. $form->{"datepaid_$i"}
  485. ) || $form->dberror($query);
  486. }
  487. if ($keepcleared) {
  488. $cleared = ($form->{"cleared_$i"}) ? 1 : 0;
  489. }
  490. # record payment
  491. $query = qq|
  492. INSERT INTO acc_trans
  493. (trans_id, chart_id, amount,
  494. transdate, source, memo, cleared)
  495. VALUES (?, (SELECT id FROM chart
  496. WHERE accno = ?),
  497. ?, ?, ?, ?, ?)|;
  498. $sth = $dbh->prepare($query);
  499. $sth->execute(
  500. $form->{id}, $accno, $form->{"paid_$i"},
  501. $form->{"datepaid_$i"}, $form->{"source_$i"},
  502. $form->{"memo_$i"}, $cleared
  503. ) || $form->dberror($query);
  504. # exchangerate difference
  505. $amount = $form->round_amount(
  506. $form->{"paid_$i"}
  507. * $form->{"exchangerate_$i"}
  508. - $form->{"paid_$i"},
  509. 2);
  510. if ($amount) {
  511. $query = qq|
  512. INSERT INTO acc_trans
  513. (trans_id, chart_id, amount,
  514. transdate, source,
  515. fx_transaction, cleared)
  516. VALUES (?, (SELECT id FROM chart
  517. WHERE accno = ?),
  518. ?, ?, ?, '1', ?)|;
  519. $sth = $dbh->prepare($query);
  520. $sth->execute(
  521. $form->{id}, $accno, $amount,
  522. $form->{"datepaid_$i"},
  523. $form->{"source_$i"}, $cleared
  524. ) || $form->dberror($query);
  525. }
  526. # gain/loss
  527. $amount = $form->round_amount(
  528. $form->round_amount(
  529. $form->{"paid_$i"}
  530. * $form->{exchangerate},2)
  531. - $form->round_amount(
  532. $form->{"paid_$i"}
  533. * $form->{"exchangerate_$i"},2)
  534. , 2);
  535. if ($amount) {
  536. my $accno_id =
  537. ($amount > 0)
  538. ? $fxgain_accno_id
  539. : $fxloss_accno_id;
  540. $query = qq|
  541. INSERT INTO acc_trans
  542. (trans_id, chart_id, amount,
  543. transdate, fx_transaction,
  544. cleared)
  545. VALUES (?, ?, ?, ?, '1', ?)|;
  546. $sth = $dbh->prepare($query);
  547. $sth->execute(
  548. $form->{id}, $accno_id, $amount,
  549. $form->{"datepaid_$i"}, $cleared
  550. ) || $form->dberror($query);
  551. }
  552. # update exchange rate
  553. if (($form->{currency} ne $form->{defaultcurrency})
  554. && !$exchangerate) {
  555. $form->update_exchangerate(
  556. $dbh, $form->{currency},
  557. $form->{"datepaid_$i"}, 0,
  558. $form->{"exchangerate_$i"});
  559. }
  560. }
  561. }
  562. # set values which could be empty
  563. $form->{taxincluded} *= 1;
  564. $form->{invnumber} =
  565. $form->update_defaults($myconfig, "vinumber", $dbh)
  566. unless $form->{invnumber};
  567. # save AP record
  568. $query = qq|
  569. UPDATE ap
  570. SET invnumber = ?,
  571. ordnumber = ?,
  572. quonumber = ?,
  573. transdate = ?,
  574. vendor_id = ?,
  575. amount = ?,
  576. netamount = ?,
  577. paid = ?,
  578. datepaid = ?,
  579. duedate = ?,
  580. invoice = '1',
  581. shippingpoint = ?,
  582. shipvia = ?,
  583. taxincluded = ?,
  584. notes = ?,
  585. intnotes = ?,
  586. curr = ?,
  587. department_id = ?,
  588. employee_id = ?,
  589. language_code = ?,
  590. ponumber = ?
  591. WHERE id = ?|;
  592. $sth = $dbh->prepare($query);
  593. $sth->execute(
  594. $form->{invnumber}, $form->{ordnumber}, $form->{quonumber},
  595. $form->{transdate}, $form->{vendor_id}, $invamount,
  596. $invnetamount, $form->{paid}, $form->{datepaid},
  597. $form->{duedate}, $form->{shippingpoint}, $form->{shipvia},
  598. $form->{taxincluded}, $form->{notes}, $form->{intnotes},
  599. $form->{currency}, $form->{department_id}, $form->{employee_id},
  600. $form->{language_code}, $form->{ponumber}, $form->{id}
  601. ) || $form->dberror($query);
  602. # add shipto
  603. $form->{name} = $form->{vendor};
  604. $form->{name} =~ s/--$form->{vendor_id}//;
  605. $form->add_shipto($dbh, $form->{id});
  606. my %audittrail = (
  607. tablename => 'ap',
  608. reference => $form->{invnumber},
  609. formname => $form->{type},
  610. action => 'posted',
  611. id => $form->{id} );
  612. $form->audittrail($dbh, "", \%audittrail);
  613. my $rc = $dbh->commit;
  614. foreach $item (keys %updparts) {
  615. $item = $dbh->quote($item);
  616. $query = qq|
  617. UPDATE parts
  618. SET avgcost = avgcost(?),
  619. lastcost = lastcost(?)
  620. WHERE id = ?|;
  621. $dbh->do($query) || $form->dberror($query);
  622. $dbh->commit;
  623. }
  624. $rc;
  625. }
  626. sub reverse_invoice {
  627. my ($dbh, $form) = @_;
  628. my $query = qq|SELECT id FROM ap
  629. WHERE id = $form->{id}|;
  630. my ($id) = $dbh->selectrow_array($query);
  631. return unless $id;
  632. # reverse inventory items
  633. $query = qq|
  634. SELECT i.parts_id, p.inventory_accno_id, p.expense_accno_id,
  635. i.qty, i.allocated, i.sellprice, i.project_id
  636. FROM invoice i, parts p
  637. WHERE i.parts_id = p.id
  638. AND i.trans_id = ?|;
  639. my $sth = $dbh->prepare($query);
  640. $sth->execute($form->{id}) || $form->dberror($query);
  641. my $netamount = 0;
  642. while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
  643. $netamount += $form->round_amount($ref->{sellprice} * $ref->{qty} * -1, 2);
  644. if ($ref->{inventory_accno_id}) {
  645. # update onhand
  646. $form->update_balance($dbh,
  647. "parts",
  648. "onhand",
  649. qq|id = $ref->{parts_id}|,
  650. $ref->{qty});
  651. # if $ref->{allocated} > 0 than we sold that many items
  652. if ($ref->{allocated} > 0) {
  653. # get references for sold items
  654. $query = qq|
  655. SELECT i.id, i.trans_id, i.allocated,
  656. a.transdate
  657. FROM invoice i, ar a
  658. WHERE i.parts_id = ?
  659. AND i.allocated < 0
  660. AND i.trans_id = a.id
  661. ORDER BY transdate DESC|;
  662. my $sth = $dbh->prepare($query);
  663. $sth->execute($ref->{parts_id})
  664. || $form->dberror($query);
  665. while (my $pthref =
  666. $sth->fetchrow_hashref(NAME_lc)) {
  667. my $qty = $ref->{allocated};
  668. if (($ref->{allocated} +
  669. $pthref->{allocated})
  670. > 0) {
  671. $qty = $pthref->{allocated}
  672. * -1;
  673. }
  674. my $amount = $form->round_amount(
  675. $ref->{sellprice} * $qty, 2);
  676. #adjust allocated
  677. $form->update_balance(
  678. $dbh, "invoice", "allocated",
  679. qq|id = $pthref->{id}|, $qty);
  680. # add reversal for sale
  681. $ref->{project_id} *= 1;
  682. $query = qq|
  683. INSERT INTO acc_trans
  684. (trans_id,
  685. chart_id, amount,
  686. transdate,
  687. project_id)
  688. VALUES (?, ?, ?, ?, ?)|;
  689. $sth = $dbh->prepare($query);
  690. $sth->execute(
  691. $pthref->{trans_id},
  692. $ref->{expense_accno_id},
  693. $amount, $form->{transdate},
  694. $ref->{project_id}
  695. ) || $form->dberror($query);
  696. $query = qq|
  697. INSERT INTO acc_trans
  698. (trans_id, chart_id,
  699. amount, transdate,
  700. project_id)
  701. VALUES (?, ?, ?, ?, ?)|;
  702. $sth = $dbh->do($query);
  703. $sth->execute(
  704. $pthref->{trans_id},
  705. $ref->{inventory_accno_id},
  706. $amount * -1,
  707. $form->{transdate},
  708. $ref->{project_id}
  709. ) || $form->dberror($query);
  710. last if (($ref->{allocated} -= $qty)
  711. <= 0);
  712. }
  713. $sth->finish;
  714. }
  715. }
  716. }
  717. $sth->finish;
  718. # delete acc_trans
  719. $query = qq|DELETE FROM acc_trans WHERE trans_id = ?|;
  720. $dbh->prepare($query);
  721. $sth->execute($form->{id}) || $form->dberror($query);
  722. # delete invoice entries
  723. $query = qq|DELETE FROM invoice WHERE trans_id = ?|;
  724. $dbh->prepare($query);
  725. $sth->execute($form->{id}) || $form->dberror($query);
  726. $query = qq|DELETE FROM shipto WHERE trans_id = ?|;
  727. $dbh->prepare($query);
  728. $sth->execute($form->{id}) || $form->dberror($query);
  729. $dbh->commit;
  730. }
  731. sub delete_invoice {
  732. my ($self, $myconfig, $form) = @_;
  733. # connect to database
  734. my $dbh = $form->{dbh};
  735. my %audittrail = (
  736. tablename => 'ap',
  737. reference => $form->{invnumber},
  738. formname => $form->{type},
  739. action => 'deleted',
  740. id => $form->{id} );
  741. $form->audittrail($dbh, "", \%audittrail);
  742. my $query = qq|SELECT parts_id FROM invoice WHERE trans_id = ?|;
  743. my $sth = $dbh->prepare($query);
  744. $sth->execute($form->{id}) || $form->dberror($query);
  745. my $item;
  746. my %updparts = ();
  747. while (($item) = $sth->fetchrow_array) {
  748. $updparts{$item} = 1;
  749. }
  750. $sth->finish;
  751. &reverse_invoice($dbh, $form);
  752. # delete AP record
  753. $query = qq|DELETE FROM ap WHERE id = ?|;
  754. my $sth = $dbh->prepare($query);
  755. $sth->execute($form->{id}) || $form->dberror($query);
  756. # delete spool files
  757. $query = qq|
  758. SELECT spoolfile FROM status
  759. WHERE trans_id = ?
  760. AND spoolfile IS NOT NULL|;
  761. my $sth = $dbh->prepare($query);
  762. $sth->execute($form->{id}) || $form->dberror($query);
  763. my $spoolfile;
  764. my @spoolfiles = ();
  765. while (($spoolfile) = $sth->fetchrow_array) {
  766. push @spoolfiles, $spoolfile;
  767. }
  768. $sth->finish;
  769. # delete status entries
  770. $query = qq|DELETE FROM status WHERE trans_id = ?|;
  771. my $sth = $dbh->prepare($query);
  772. $sth->execute($form->{id}) || $form->dberror($query);
  773. if ($rc) {
  774. foreach $item (keys %updparts) {
  775. $item = $dbh->quote($item);
  776. $query = qq|
  777. UPDATE parts
  778. SET avgcost = avgcost($item),
  779. lastcost = lastcost($item)
  780. WHERE id = $item|;
  781. $dbh->do($query) || $form->dberror($query);
  782. }
  783. foreach $spoolfile (@spoolfiles) {
  784. unlink "${LedgerSMB::Sysconfig::spool}/$spoolfile"
  785. if $spoolfile;
  786. }
  787. }
  788. my $rc = $dbh->commit;
  789. $rc;
  790. }
  791. sub retrieve_invoice {
  792. my ($self, $myconfig, $form) = @_;
  793. # connect to database
  794. my $dbh = $form->dbconnect_noauto($myconfig);
  795. my $query;
  796. if ($form->{id}) {
  797. # get default accounts and last invoice number
  798. $query = qq|
  799. SELECT (SELECT c.accno FROM chart c
  800. WHERE d.inventory_accno_id = c.id)
  801. AS inventory_accno,
  802. (SELECT c.accno FROM chart c
  803. WHERE d.income_accno_id = c.id)
  804. AS income_accno,
  805. (SELECT c.accno FROM chart c
  806. WHERE d.expense_accno_id = c.id)
  807. AS expense_accno,
  808. (SELECT c.accno FROM chart c
  809. WHERE d.fxgain_accno_id = c.id)
  810. AS fxgain_accno,
  811. (SELECT c.accno FROM chart c
  812. WHERE d.fxloss_accno_id = c.id)
  813. AS fxloss_accno, d.curr AS currencies
  814. FROM defaults d|;
  815. } else {
  816. $query = qq|
  817. SELECT (SELECT c.accno FROM chart c
  818. WHERE d.inventory_accno_id = c.id)
  819. AS inventory_accno,
  820. (SELECT c.accno FROM chart c
  821. WHERE d.income_accno_id = c.id)
  822. AS income_accno,
  823. (SELECT c.accno FROM chart c
  824. WHERE d.expense_accno_id = c.id)
  825. AS expense_accno,
  826. (SELECT c.accno FROM chart c
  827. WHERE d.fxgain_accno_id = c.id)
  828. AS fxgain_accno,
  829. (SELECT c.accno FROM chart c
  830. WHERE d.fxloss_accno_id = c.id)
  831. AS fxloss_accno, d.curr AS currencies,
  832. current_date AS transdate
  833. FROM defaults d|;
  834. }
  835. my $sth = $dbh->prepare($query);
  836. $sth->execute || $form->dberror($query);
  837. my $ref = $sth->fetchrow_hashref(NAME_lc);
  838. for (keys %$ref) {
  839. $form->{$_} = $ref->{$_};
  840. }
  841. $sth->finish;
  842. if ($form->{id}) {
  843. $query = qq|
  844. SELECT a.invnumber, a.transdate, a.duedate,
  845. a.ordnumber, a.quonumber, a.paid, a.taxincluded,
  846. a.notes, a.intnotes, a.curr AS currency,
  847. a.vendor_id, a.language_code, a.ponumber
  848. FROM ap a
  849. WHERE id = ?|;
  850. $sth = $dbh->prepare($query);
  851. $sth->execute($form->{id}) || $form->dberror($query);
  852. $ref = $sth->fetchrow_hashref(NAME_lc);
  853. for (keys %$ref) {
  854. $form->{$_} = $ref->{$_};
  855. }
  856. $sth->finish;
  857. # get shipto
  858. $query = qq|SELECT * FROM shipto WHERE trans_id = ?|;
  859. $sth = $dbh->prepare($query);
  860. $sth->execute($form->{id}) || $form->dberror($query);
  861. $ref = $sth->fetchrow_hashref(NAME_lc);
  862. for (keys %$ref) {
  863. $form->{$_} = $ref->{$_};
  864. }
  865. $sth->finish;
  866. # retrieve individual items
  867. $query = qq|
  868. SELECT p.partnumber, i.description, i.qty,
  869. i.fxsellprice, i.sellprice,
  870. i.parts_id AS id, i.unit, p.bin,
  871. i.deliverydate,
  872. pr.projectnumber, i.project_id,
  873. i.serialnumber,
  874. i.discount, i.notes, pg.partsgroup,
  875. p.partsgroup_id, p.partnumber AS sku,
  876. p.weight, p.onhand, p.inventory_accno_id,
  877. p.income_accno_id, p.expense_accno_id,
  878. t.description AS partsgrouptranslation
  879. FROM invoice i
  880. JOIN parts p ON (i.parts_id = p.id)
  881. LEFT JOIN project pr ON (i.project_id = pr.id)
  882. LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
  883. LEFT JOIN translation t
  884. ON (t.trans_id = p.partsgroup_id
  885. AND t.language_code = ?)
  886. WHERE i.trans_id = ?
  887. ORDER BY i.id|;
  888. $sth = $dbh->prepare($query);
  889. $sth->execute($form->{language_code}, $form->{id})
  890. || $form->dberror($query);
  891. # exchangerate defaults
  892. &exchangerate_defaults($dbh, $form);
  893. # price matrix and vendor partnumber
  894. my $pmh = PriceMatrix::PriceMatrixQuery($dbh, $form);
  895. # tax rates for part
  896. $query = qq|
  897. SELECT c.accno
  898. FROM chart c
  899. JOIN partstax pt ON (pt.chart_id = c.id)
  900. WHERE pt.parts_id = ?|;
  901. my $tth = $dbh->prepare($query);
  902. my $ptref;
  903. while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
  904. my ($dec) = ($ref->{fxsellprice} =~ /\.(\d+)/);
  905. $dec = length $dec;
  906. my $decimalplaces = ($dec > 2) ? $dec : 2;
  907. $tth->execute($ref->{id});
  908. $ref->{taxaccounts} = "";
  909. my $taxrate = 0;
  910. while ($ptref = $tth->fetchrow_hashref(NAME_lc)) {
  911. $ref->{taxaccounts} .= "$ptref->{accno} ";
  912. $taxrate += $form->{"$ptref->{accno}_rate"};
  913. }
  914. $tth->finish;
  915. chop $ref->{taxaccounts};
  916. # price matrix
  917. $ref->{sellprice} = $form->round_amount(
  918. $ref->{fxsellprice}
  919. * $form->{$form->{currency}},
  920. $decimalplaces);
  921. PriceMatrix::price_matrix(
  922. $pmh, $ref, $decimalplaces, $form, $myconfig);
  923. $ref->{sellprice} = $ref->{fxsellprice};
  924. $ref->{qty} *= -1;
  925. $ref->{partsgroup} = $ref->{partsgrouptranslation}
  926. if $ref->{partsgrouptranslation};
  927. push @{ $form->{invoice_details} }, $ref;
  928. }
  929. $sth->finish;
  930. }
  931. my $rc = $dbh->commit;
  932. $rc;
  933. }
  934. sub retrieve_item {
  935. my ($self, $myconfig, $form) = @_;
  936. my $i = $form->{rowcount};
  937. my $null;
  938. my $var;
  939. # don't include assemblies or obsolete parts
  940. my $where = "WHERE p.assembly = '0' AND p.obsolete = '0'";
  941. if ($form->{"partnumber_$i"} ne "") {
  942. $var = $dbh->quote($form->like(lc $form->{"partnumber_$i"}));
  943. $where .= " AND lower(p.partnumber) LIKE $var";
  944. }
  945. if ($form->{"description_$i"} ne "") {
  946. $var = $dbh->quote($form->$like(lc $form->{"description_$i"}));
  947. if ($form->{language_code} ne "") {
  948. $where .= " AND lower(t1.description) LIKE $var";
  949. } else {
  950. $where .= " AND lower(p.description) LIKE $var";
  951. }
  952. }
  953. if ($form->{"partsgroup_$i"} ne "") {
  954. ($null, $var) = split /--/, $form->{"partsgroup_$i"};
  955. $var = $dbh->quote($var);
  956. $where .= qq| AND p.partsgroup_id = $var|;
  957. }
  958. if ($form->{"description_$i"} ne "") {
  959. $where .= " ORDER BY 3";
  960. } else {
  961. $where .= " ORDER BY 2";
  962. }
  963. # connect to database
  964. my $dbh = $form->dbconnect($myconfig);
  965. my $query = qq|
  966. SELECT p.id, p.partnumber, p.description,
  967. pg.partsgroup, p.partsgroup_id,
  968. p.lastcost AS sellprice, p.unit, p.bin, p.onhand,
  969. p.notes, p.inventory_accno_id, p.income_accno_id,
  970. p.expense_accno_id, p.partnumber AS sku, p.weight,
  971. t1.description AS translation,
  972. t2.description AS grouptranslation
  973. FROM parts p
  974. LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
  975. LEFT JOIN translation t1
  976. ON (t1.trans_id = p.id AND t1.language_code = ?)
  977. LEFT JOIN translation t2
  978. ON (t2.trans_id = p.partsgroup_id
  979. AND t2.language_code = ?)
  980. $where|;
  981. my $sth = $dbh->prepare($query);
  982. $sth->execute($form->{language_code}, $form->{language_code})
  983. || $form->dberror($query);
  984. # foreign currency
  985. &exchangerate_defaults($dbh, $form);
  986. # taxes
  987. $query = qq|
  988. SELECT c.accno
  989. FROM chart c
  990. JOIN partstax pt ON (pt.chart_id = c.id)
  991. WHERE pt.parts_id = ?|;
  992. my $tth = $dbh->prepare($query) || $form->dberror($query);
  993. # price matrix
  994. my $pmh = PriceMatrix::price_matrix_query($dbh, $form);
  995. my $ref;
  996. my $ptref;
  997. while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
  998. my ($dec) = ($ref->{sellprice} =~ /\.(\d+)/);
  999. $dec = length $dec;
  1000. my $decimalplaces = ($dec > 2) ? $dec : 2;
  1001. # get taxes for part
  1002. $tth->execute($ref->{id});
  1003. $ref->{taxaccounts} = "";
  1004. while ($ptref = $tth->fetchrow_hashref(NAME_lc)) {
  1005. $ref->{taxaccounts} .= "$ptref->{accno} ";
  1006. }
  1007. $tth->finish;
  1008. chop $ref->{taxaccounts};
  1009. # get vendor price and partnumber
  1010. PriceMatrix::price_matrix(
  1011. $pmh, $ref, $decimalplaces, $form, $myconfig);
  1012. $ref->{description} = $ref->{translation}
  1013. if $ref->{translation};
  1014. $ref->{partsgroup} = $ref->{grouptranslation}
  1015. if $ref->{grouptranslation};
  1016. push @{ $form->{item_list} }, $ref;
  1017. }
  1018. $sth->finish;
  1019. $dbh->commit;
  1020. }
  1021. sub exchangerate_defaults {
  1022. my ($dbh, $form) = @_;
  1023. my $var;
  1024. # get default currencies
  1025. my $query = qq|SELECT substr(curr,1,3), curr FROM defaults|;
  1026. my $eth = $dbh->prepare($query) || $form->dberror($query);
  1027. $eth->execute;
  1028. ($form->{defaultcurrency}, $form->{currencies}) = $eth->fetchrow_array;
  1029. $eth->finish;
  1030. $query = qq|
  1031. SELECT sell
  1032. FROM exchangerate
  1033. WHERE curr = ?
  1034. AND transdate = ?|;
  1035. my $eth1 = $dbh->prepare($query) || $form->dberror($query);
  1036. $query = qq/
  1037. SELECT max(transdate || ' ' || sell || ' ' || curr)
  1038. FROM exchangerate
  1039. WHERE curr = ?/;
  1040. my $eth2 = $dbh->prepare($query) || $form->dberror($query);
  1041. # get exchange rates for transdate or max
  1042. foreach $var (split /:/, substr($form->{currencies},4)) {
  1043. $eth1->execute($var, $form->{transdate});
  1044. ($form->{$var}) = $eth1->fetchrow_array;
  1045. if (! $form->{$var} ) {
  1046. $eth2->execute($var);
  1047. ($form->{$var}) = $eth2->fetchrow_array;
  1048. ($null, $form->{$var}) = split / /, $form->{$var};
  1049. $form->{$var} = 1 unless $form->{$var};
  1050. $eth2->finish;
  1051. }
  1052. $eth1->finish;
  1053. }
  1054. $form->{$form->{currency}} = $form->{exchangerate}
  1055. if $form->{exchangerate};
  1056. $form->{$form->{currency}} ||= 1;
  1057. $form->{$form->{defaultcurrency}} = 1;
  1058. }
  1059. sub vendor_details {
  1060. my ($self, $myconfig, $form) = @_;
  1061. # connect to database
  1062. my $dbh = $form->{dbh};
  1063. # get rest for the vendor
  1064. my $query = qq|
  1065. SELECT vendornumber, name, address1, address2, city, state,
  1066. zipcode, country, contact, phone as vendorphone,
  1067. fax as vendorfax, vendornumber,
  1068. taxnumber AS vendortaxnumber, sic_code AS sic, iban, bic,
  1069. gifi_accno AS gifi, startdate, enddate
  1070. FROM vendor
  1071. WHERE id = ?|;
  1072. my $sth = $dbh->prepare($query);
  1073. $sth->execute($form->{vendor_id}) || $form->dberror($query);
  1074. $ref = $sth->fetchrow_hashref(NAME_lc);
  1075. for (keys %$ref) {
  1076. $form->{$_} = $ref->{$_};
  1077. }
  1078. $sth->finish;
  1079. $dbh->commit;
  1080. }
  1081. sub item_links {
  1082. my ($self, $myconfig, $form) = @_;
  1083. # connect to database
  1084. my $dbh = $form->dbconnect($myconfig);
  1085. my $query = qq|
  1086. SELECT accno, description, link
  1087. FROM chart
  1088. WHERE link LIKE '%IC%'
  1089. ORDER BY accno|;
  1090. my $sth = $dbh->prepare($query);
  1091. $sth->execute || $form->dberror($query);
  1092. while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
  1093. foreach my $key (split(/:/, $ref->{link})) {
  1094. if ($key =~ /IC/) {
  1095. push @{ $form->{IC_links}{$key} },
  1096. { accno => $ref->{accno},
  1097. description => $ref->{description} };
  1098. }
  1099. }
  1100. }
  1101. $sth->finish;
  1102. }
  1103. 1;