summaryrefslogtreecommitdiff
path: root/LedgerSMB/IS.pm
blob: 5bb7ef638b2f77273676816a53d82a64c3b1134c (plain)
  1. #=====================================================================
  2. # LedgerSMB
  3. # Small Medium Business Accounting software
  4. #
  5. # See COPYRIGHT file for copyright information
  6. #======================================================================
  7. #
  8. # This file has NOT undergone whitespace cleanup.
  9. #
  10. #======================================================================
  11. #
  12. # Inventory invoicing module
  13. #
  14. #======================================================================
  15. package IS;
  16. sub invoice_details {
  17. my ($self, $myconfig, $form) = @_;
  18. $form->{duedate} = $form->{transdate} unless ($form->{duedate});
  19. # connect to database
  20. my $dbh = $form->dbconnect($myconfig);
  21. my $query = qq|SELECT date '$form->{duedate}' - date '$form->{transdate}'
  22. AS terms, weightunit
  23. FROM defaults|;
  24. my $sth = $dbh->prepare($query);
  25. $sth->execute || $form->dberror($query);
  26. ($form->{terms}, $form->{weightunit}) = $sth->fetchrow_array;
  27. $sth->finish;
  28. # this is for the template
  29. $form->{invdate} = $form->{transdate};
  30. my $tax = 0;
  31. my $item;
  32. my $i;
  33. my @sortlist = ();
  34. my $projectnumber;
  35. my $projectdescription;
  36. my $projectnumber_id;
  37. my $translation;
  38. my $partsgroup;
  39. my %oid = ( 'Pg' => 'oid',
  40. 'PgPP' => 'oid',
  41. 'Oracle' => 'rowid',
  42. 'DB2' => '1=1'
  43. );
  44. my @taxaccounts;
  45. my %taxaccounts;
  46. my $tax;
  47. my $taxrate;
  48. my $taxamount;
  49. my %translations;
  50. $query = qq|SELECT p.description, t.description
  51. FROM project p
  52. LEFT JOIN translation t ON (t.trans_id = p.id AND t.language_code = '$form->{language_code}')
  53. WHERE id = ?|;
  54. my $prh = $dbh->prepare($query) || $form->dberror($query);
  55. $query = qq|SELECT inventory_accno_id, income_accno_id,
  56. expense_accno_id, assembly, weight FROM parts
  57. WHERE id = ?|;
  58. my $pth = $dbh->prepare($query) || $form->dberror($query);
  59. my $sortby;
  60. # sort items by project and partsgroup
  61. for $i (1 .. $form->{rowcount} - 1) {
  62. # account numbers
  63. $pth->execute($form->{"id_$i"});
  64. $ref = $pth->fetchrow_hashref(NAME_lc);
  65. for (keys %$ref) { $form->{"${_}_$i"} = $ref->{$_} }
  66. $pth->finish;
  67. $projectnumber_id = 0;
  68. $projectnumber = "";
  69. $form->{partsgroup} = "";
  70. $form->{projectnumber} = "";
  71. if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) {
  72. $inventory_accno_id = ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) ? "1" : "";
  73. if ($form->{groupprojectnumber}) {
  74. ($projectnumber, $projectnumber_id) = split /--/, $form->{"projectnumber_$i"};
  75. }
  76. if ($form->{grouppartsgroup}) {
  77. ($form->{partsgroup}) = split /--/, $form->{"partsgroup_$i"};
  78. }
  79. if ($projectnumber_id && $form->{groupprojectnumber}) {
  80. if ($translation{$projectnumber_id}) {
  81. $form->{projectnumber} = $translation{$projectnumber_id};
  82. } else {
  83. # get project description
  84. $prh->execute($projectnumber_id);
  85. ($projectdescription, $translation) = $prh->fetchrow_array;
  86. $prh->finish;
  87. $form->{projectnumber} = ($translation) ? "$projectnumber, $translation" : "$projectnumber, $projectdescription";
  88. $translation{$projectnumber_id} = $form->{projectnumber};
  89. }
  90. }
  91. if ($form->{grouppartsgroup} && $form->{partsgroup}) {
  92. $form->{projectnumber} .= " / " if $projectnumber_id;
  93. $form->{projectnumber} .= $form->{partsgroup};
  94. }
  95. $form->format_string(projectnumber);
  96. }
  97. $sortby = qq|$projectnumber$form->{partsgroup}|;
  98. if ($form->{sortby} ne 'runningnumber') {
  99. for (qw(partnumber description bin)) {
  100. $sortby .= $form->{"${_}_$i"} if $form->{sortby} eq $_;
  101. }
  102. }
  103. push @sortlist, [ $i, qq|$projectnumber$form->{partsgroup}$inventory_accno_id|, $form->{projectnumber}, $projectnumber_id, $form->{partsgroup}, $sortby ];
  104. }
  105. # sort the whole thing by project and group
  106. @sortlist = sort { $a->[5] cmp $b->[5] } @sortlist;
  107. my $runningnumber = 1;
  108. my $sameitem = "";
  109. my $subtotal;
  110. my $k = scalar @sortlist;
  111. my $j = 0;
  112. foreach $item (@sortlist) {
  113. $i = $item->[0];
  114. $j++;
  115. # heading
  116. if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) {
  117. if ($item->[1] ne $sameitem) {
  118. $sameitem = $item->[1];
  119. $ok = 0;
  120. if ($form->{groupprojectnumber}) {
  121. $ok = $form->{"projectnumber_$i"};
  122. }
  123. if ($form->{grouppartsgroup}) {
  124. $ok = $form->{"partsgroup_$i"} unless $ok;
  125. }
  126. if ($ok) {
  127. if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) {
  128. push(@{ $form->{part} }, "");
  129. push(@{ $form->{service} }, NULL);
  130. } else {
  131. push(@{ $form->{part} }, NULL);
  132. push(@{ $form->{service} }, "");
  133. }
  134. push(@{ $form->{description} }, $item->[2]);
  135. for (qw(taxrates runningnumber number sku serialnumber bin qty ship unit deliverydate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") }
  136. push(@{ $form->{lineitems} }, { amount => 0, tax => 0 });
  137. }
  138. }
  139. }
  140. $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
  141. if ($form->{"qty_$i"}) {
  142. $form->{totalqty} += $form->{"qty_$i"};
  143. $form->{totalship} += $form->{"qty_$i"};
  144. $form->{totalweight} += ($form->{"qty_$i"} * $form->{"weight_$i"});
  145. $form->{totalweightship} += ($form->{"qty_$i"} * $form->{"weight_$i"});
  146. # add number, description and qty to $form->{number}, ....
  147. push(@{ $form->{runningnumber} }, $runningnumber++);
  148. push(@{ $form->{number} }, $form->{"partnumber_$i"});
  149. push(@{ $form->{sku} }, $form->{"sku_$i"});
  150. push(@{ $form->{serialnumber} }, $form->{"serialnumber_$i"});
  151. push(@{ $form->{bin} }, $form->{"bin_$i"});
  152. push(@{ $form->{description} }, $form->{"description_$i"});
  153. push(@{ $form->{itemnotes} }, $form->{"notes_$i"});
  154. push(@{ $form->{qty} }, $form->format_amount($myconfig, $form->{"qty_$i"}));
  155. push(@{ $form->{ship} }, $form->format_amount($myconfig, $form->{"qty_$i"}));
  156. push(@{ $form->{unit} }, $form->{"unit_$i"});
  157. push(@{ $form->{deliverydate} }, $form->{"deliverydate_$i"});
  158. push(@{ $form->{projectnumber} }, $form->{"projectnumber_$i"});
  159. push(@{ $form->{sellprice} }, $form->{"sellprice_$i"});
  160. # listprice
  161. push(@{ $form->{listprice} }, $form->{"listprice_$i"});
  162. push(@{ $form->{weight} }, $form->format_amount($myconfig, $form->{"weight_$i"} * $form->{"qty_$i"}));
  163. my $sellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
  164. my ($dec) = ($sellprice =~ /\.(\d+)/);
  165. $dec = length $dec;
  166. my $decimalplaces = ($dec > 2) ? $dec : 2;
  167. my $discount = $form->round_amount($sellprice * $form->parse_amount($myconfig, $form->{"discount_$i"})/100, $decimalplaces);
  168. # keep a netprice as well, (sellprice - discount)
  169. $form->{"netprice_$i"} = $sellprice - $discount;
  170. my $linetotal = $form->round_amount($form->{"qty_$i"} * $form->{"netprice_$i"}, 2);
  171. if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) {
  172. push(@{ $form->{part} }, $form->{"sku_$i"});
  173. push(@{ $form->{service} }, NULL);
  174. $form->{totalparts} += $linetotal;
  175. } else {
  176. push(@{ $form->{service} }, $form->{"sku_$i"});
  177. push(@{ $form->{part} }, NULL);
  178. $form->{totalservices} += $linetotal;
  179. }
  180. push(@{ $form->{netprice} }, ($form->{"netprice_$i"}) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : " ");
  181. $discount = ($discount) ? $form->format_amount($myconfig, $discount * -1, $decimalplaces) : " ";
  182. $linetotal = ($linetotal) ? $linetotal : " ";
  183. push(@{ $form->{discount} }, $discount);
  184. push(@{ $form->{discountrate} }, $form->format_amount($myconfig, $form->{"discount_$i"}));
  185. $form->{total} += $linetotal;
  186. # this is for the subtotals for grouping
  187. $subtotal += $linetotal;
  188. $form->{"linetotal_$i"} = $form->format_amount($myconfig, $linetotal, 2);
  189. push(@{ $form->{linetotal} }, $form->{"linetotal_$i"});
  190. @taxaccounts = split / /, $form->{"taxaccounts_$i"};
  191. my $ml = 1;
  192. my @taxrates = ();
  193. $tax = 0;
  194. for (0 .. 1) {
  195. $taxrate = 0;
  196. for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 }
  197. $taxrate *= $ml;
  198. $taxamount = $linetotal * $taxrate / (1 + $taxrate);
  199. $taxbase = ($linetotal - $taxamount);
  200. foreach $item (@taxaccounts) {
  201. if (($form->{"${item}_rate"} * $ml) > 0) {
  202. push @taxrates, $form->{"${item}_rate"} * 100;
  203. if ($form->{taxincluded}) {
  204. $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"} / (1 + $taxrate);
  205. $taxbase{$item} += $taxbase;
  206. } else {
  207. $taxbase{$item} += $linetotal;
  208. $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"};
  209. }
  210. }
  211. }
  212. if ($form->{taxincluded}) {
  213. $tax += $linetotal * ($taxrate / (1 + ($taxrate * $ml)));
  214. } else {
  215. $tax += $linetotal * $taxrate;
  216. }
  217. $ml *= -1;
  218. }
  219. push(@{ $form->{lineitems} }, { amount => $linetotal, tax => $form->round_amount($tax, 2) });
  220. push(@{ $form->{taxrates} }, join ' ', sort { $a <=> $b } @taxrates);
  221. if ($form->{"assembly_$i"}) {
  222. $form->{stagger} = -1;
  223. &assembly_details($myconfig, $form, $dbh, $form->{"id_$i"}, $oid{$myconfig->{dbdriver}}, $form->{"qty_$i"});
  224. }
  225. }
  226. # add subtotal
  227. if ($form->{groupprojectnumber} || $form->{grouppartsgroup}) {
  228. if ($subtotal) {
  229. if ($j < $k) {
  230. # look at next item
  231. if ($sortlist[$j]->[1] ne $sameitem) {
  232. if ($form->{"inventory_accno_id_$j"} || $form->{"assembly_$i"}) {
  233. push(@{ $form->{part} }, "");
  234. push(@{ $form->{service} }, NULL);
  235. } else {
  236. push(@{ $form->{service} }, "");
  237. push(@{ $form->{part} }, NULL);
  238. }
  239. for (qw(taxrates runningnumber number sku serialnumber bin qty ship unit deliverydate projectnumber sellprice listprice netprice discount discountrate weight itemnotes)) { push(@{ $form->{$_} }, "") }
  240. push(@{ $form->{description} }, $form->{groupsubtotaldescription});
  241. push(@{ $form->{lineitems} }, { amount => 0, tax => 0 });
  242. if ($form->{groupsubtotaldescription} ne "") {
  243. push(@{ $form->{linetotal} }, $form->format_amount($myconfig, $subtotal, 2));
  244. } else {
  245. push(@{ $form->{linetotal} }, "");
  246. }
  247. $subtotal = 0;
  248. }
  249. } else {
  250. # got last item
  251. if ($form->{groupsubtotaldescription} ne "") {
  252. if ($form->{"inventory_accno_id_$j"} || $form->{"assembly_$i"}) {
  253. push(@{ $form->{part} }, "");
  254. push(@{ $form->{service} }, NULL);
  255. } else {
  256. push(@{ $form->{service} }, "");
  257. push(@{ $form->{part} }, NULL);
  258. }
  259. for (qw(taxrates runningnumber number sku serialnumber bin qty ship unit deliverydate projectnumber sellprice listprice netprice discount discountrate weight itemnotes)) { push(@{ $form->{$_} }, "") }
  260. push(@{ $form->{description} }, $form->{groupsubtotaldescription});
  261. push(@{ $form->{linetotal} }, $form->format_amount($myconfig, $subtotal, 2));
  262. push(@{ $form->{lineitems} }, { amount => 0, tax => 0 });
  263. }
  264. }
  265. }
  266. }
  267. }
  268. $tax = 0;
  269. foreach my $item (sort keys %taxaccounts) {
  270. if ($form->round_amount($taxaccounts{$item}, 2)) {
  271. $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
  272. push(@{ $form->{taxbaseinclusive} }, $form->{"${item}_taxbaseinclusive"} = $form->format_amount($myconfig, $taxbase{$item} + $tax, 2));
  273. push(@{ $form->{taxbase} }, $form->{"${item}_taxbase"} = $form->format_amount($myconfig, $taxbase{$item}, 2));
  274. push(@{ $form->{tax} }, $form->{"${item}_tax"} = $form->format_amount($myconfig, $taxamount, 2));
  275. push(@{ $form->{taxdescription} }, $form->{"${item}_description"});
  276. $form->{"${item}_taxrate"} = $form->format_amount($myconfig, $form->{"${item}_rate"} * 100);
  277. push(@{ $form->{taxrate} }, $form->{"${item}_taxrate"});
  278. push(@{ $form->{taxnumber} }, $form->{"${item}_taxnumber"});
  279. }
  280. }
  281. # adjust taxes for lineitems
  282. my $total = 0;
  283. for (@{ $form->{lineitems} }) {
  284. $total += $_->{tax};
  285. }
  286. if ($form->round_amount($total,2) != $form->round_amount($tax,2)) {
  287. # get largest amount
  288. for (reverse sort { $a->{tax} <=> $b->{tax} } @{ $form->{lineitems} }) {
  289. $_->{tax} -= $total - $tax;
  290. last;
  291. }
  292. }
  293. $i = 1;
  294. for (@{ $form->{lineitems} }) {
  295. push(@{ $form->{linetax} }, $form->format_amount($myconfig, $_->{tax}, 2, ""));
  296. }
  297. for $i (1 .. $form->{paidaccounts}) {
  298. if ($form->{"paid_$i"}) {
  299. push(@{ $form->{payment} }, $form->{"paid_$i"});
  300. my ($accno, $description) = split /--/, $form->{"AR_paid_$i"};
  301. push(@{ $form->{paymentaccount} }, $description);
  302. push(@{ $form->{paymentdate} }, $form->{"datepaid_$i"});
  303. push(@{ $form->{paymentsource} }, $form->{"source_$i"});
  304. push(@{ $form->{paymentmemo} }, $form->{"memo_$i"});
  305. $form->{paid} += $form->parse_amount($myconfig, $form->{"paid_$i"});
  306. }
  307. }
  308. for (qw(totalparts totalservices)) { $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) }
  309. for (qw(totalqty totalship totalweight)) { $form->{$_} = $form->format_amount($myconfig, $form->{$_}) }
  310. $form->{subtotal} = $form->format_amount($myconfig, $form->{total}, 2);
  311. $form->{invtotal} = ($form->{taxincluded}) ? $form->{total} : $form->{total} + $tax;
  312. use LedgerSMB::CP;
  313. my $c;
  314. if ($form->{language_code} ne "") {
  315. $c = new CP $form->{language_code};
  316. } else {
  317. $c = new CP $myconfig->{countrycode};
  318. }
  319. $c->init;
  320. my $whole;
  321. ($whole, $form->{decimal}) = split /\./, $form->{invtotal};
  322. $form->{decimal} .= "00";
  323. $form->{decimal} = substr($form->{decimal}, 0, 2);
  324. $form->{text_decimal} = $c->num2text($form->{decimal} * 1);
  325. $form->{text_amount} = $c->num2text($whole);
  326. $form->{integer_amount} = $form->format_amount($myconfig, $whole);
  327. $form->format_string(qw(text_amount text_decimal));
  328. $form->{total} = $form->format_amount($myconfig, $form->{invtotal} - $form->{paid}, 2);
  329. $form->{invtotal} = $form->format_amount($myconfig, $form->{invtotal}, 2);
  330. $form->{paid} = $form->format_amount($myconfig, $form->{paid}, 2);
  331. $dbh->disconnect;
  332. }
  333. sub assembly_details {
  334. my ($myconfig, $form, $dbh, $id, $oid, $qty) = @_;
  335. my $sm = "";
  336. my $spacer;
  337. $form->{stagger}++;
  338. if ($form->{format} eq 'html') {
  339. $spacer = "&nbsp;" x (3 * ($form->{stagger} - 1)) if $form->{stagger} > 1;
  340. }
  341. if ($form->{format} =~ /(postscript|pdf)/) {
  342. if ($form->{stagger} > 1) {
  343. $spacer = ($form->{stagger} - 1) * 3;
  344. $spacer = '\rule{'.$spacer.'mm}{0mm}';
  345. }
  346. }
  347. # get parts and push them onto the stack
  348. my $sortorder = "";
  349. if ($form->{grouppartsgroup}) {
  350. $sortorder = qq|ORDER BY pg.partsgroup, a.$oid|;
  351. } else {
  352. $sortorder = qq|ORDER BY a.$oid|;
  353. }
  354. my $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty,
  355. pg.partsgroup, p.partnumber AS sku
  356. FROM assembly a
  357. JOIN parts p ON (a.parts_id = p.id)
  358. LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
  359. WHERE a.bom = '1'
  360. AND a.id = '$id'
  361. $sortorder|;
  362. my $sth = $dbh->prepare($query);
  363. $sth->execute || $form->dberror($query);
  364. while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
  365. for (qw(partnumber description partsgroup)) {
  366. $form->{"a_$_"} = $ref->{$_};
  367. $form->format_string("a_$_");
  368. }
  369. if ($form->{grouppartsgroup} && $ref->{partsgroup} ne $sm) {
  370. for (qw(taxrates runningnumber number sku serialnumber unit qty ship bin deliverydate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") }
  371. $sm = ($form->{"a_partsgroup"}) ? $form->{"a_partsgroup"} : "--";
  372. push(@{ $form->{description} }, "$spacer$sm");
  373. push(@{ $form->{lineitems} }, { amount => 0, tax => 0 });
  374. }
  375. if ($form->{stagger}) {
  376. push(@{ $form->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq| -- $form->{"a_partnumber"}, $form->{"a_description"}|);
  377. for (qw(taxrates runningnumber number sku serialnumber unit qty ship bin deliverydate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") }
  378. } else {
  379. push(@{ $form->{description} }, qq|$form->{"a_description"}|);
  380. push(@{ $form->{number} }, $form->{"a_partnumber"});
  381. push(@{ $form->{sku} }, $form->{"a_partnumber"});
  382. for (qw(taxrates runningnumber ship serialnumber reqdate projectnumber sellprice listprice netprice discount discountrate linetotal weight itemnotes)) { push(@{ $form->{$_} }, "") }
  383. }
  384. push(@{ $form->{lineitems} }, { amount => 0, tax => 0 });
  385. push(@{ $form->{qty} }, $form->format_amount($myconfig, $ref->{qty} * $qty));
  386. for (qw(unit bin)) {
  387. $form->{"a_$_"} = $ref->{$_};
  388. $form->format_string("a_$_");
  389. push(@{ $form->{$_} }, $form->{"a_$_"});
  390. }
  391. }
  392. $sth->finish;
  393. $form->{stagger}--;
  394. }
  395. sub project_description {
  396. my ($self, $dbh, $id) = @_;
  397. my $query = qq|SELECT description
  398. FROM project
  399. WHERE id = $id|;
  400. ($_) = $dbh->selectrow_array($query);
  401. $_;
  402. }
  403. sub customer_details {
  404. my ($self, $myconfig, $form) = @_;
  405. # connect to database
  406. my $dbh = $form->dbconnect($myconfig);
  407. # get rest for the customer
  408. my $query = qq|SELECT customernumber, name, address1, address2, city,
  409. state, zipcode, country,
  410. contact, phone as customerphone, fax as customerfax,
  411. taxnumber AS customertaxnumber, sic_code AS sic, iban, bic,
  412. startdate, enddate
  413. FROM customer
  414. WHERE id = $form->{customer_id}|;
  415. my $sth = $dbh->prepare($query);
  416. $sth->execute || $form->dberror($query);
  417. $ref = $sth->fetchrow_hashref(NAME_lc);
  418. for (keys %$ref) { $form->{$_} = $ref->{$_} }
  419. $sth->finish;
  420. $dbh->disconnect;
  421. }
  422. sub post_invoice {
  423. my ($self, $myconfig, $form) = @_;
  424. # connect to database, turn off autocommit
  425. my $dbh = $form->dbconnect_noauto($myconfig);
  426. my $query;
  427. my $sth;
  428. my $null;
  429. my $project_id;
  430. my $exchangerate = 0;
  431. my $keepcleared = 0;
  432. %$form->{acc_trans} = ();
  433. ($null, $form->{employee_id}) = split /--/, $form->{employee};
  434. unless ($form->{employee_id}) {
  435. ($form->{employee}, $form->{employee_id}) = $form->get_employee($dbh);
  436. }
  437. ($null, $form->{department_id}) = split(/--/, $form->{department});
  438. $form->{department_id} *= 1;
  439. $query = qq|SELECT fxgain_accno_id, fxloss_accno_id
  440. FROM defaults|;
  441. my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query);
  442. $query = qq|SELECT p.assembly, p.inventory_accno_id,
  443. p.income_accno_id, p.expense_accno_id, p.project_id
  444. FROM parts p
  445. WHERE p.id = ?|;
  446. my $pth = $dbh->prepare($query) || $form->dberror($query);
  447. if ($form->{id}) {
  448. $keepcleared = 1;
  449. $query = qq|SELECT id FROM ar
  450. WHERE id = $form->{id}|;
  451. if ($dbh->selectrow_array($query)) {
  452. &reverse_invoice($dbh, $form);
  453. } else {
  454. $query = qq|INSERT INTO ar (id)
  455. VALUES ($form->{id})|;
  456. $dbh->do($query) || $form->dberror($query);
  457. }
  458. }
  459. my $uid = localtime;
  460. $uid .= "$$";
  461. if (! $form->{id}) {
  462. $query = qq|INSERT INTO ar (invnumber, employee_id)
  463. VALUES ('$uid', $form->{employee_id})|;
  464. $dbh->do($query) || $form->dberror($query);
  465. $query = qq|SELECT id FROM ar
  466. WHERE invnumber = '$uid'|;
  467. $sth = $dbh->prepare($query);
  468. $sth->execute || $form->dberror($query);
  469. ($form->{id}) = $sth->fetchrow_array;
  470. $sth->finish;
  471. }
  472. if ($form->{currency} eq $form->{defaultcurrency}) {
  473. $form->{exchangerate} = 1;
  474. } else {
  475. $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, 'buy');
  476. }
  477. $form->{exchangerate} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{exchangerate});
  478. my $i;
  479. my $item;
  480. my $allocated = 0;
  481. my $taxrate;
  482. my $tax;
  483. my $fxtax;
  484. my @taxaccounts;
  485. my $amount;
  486. my $grossamount;
  487. my $invamount = 0;
  488. my $invnetamount = 0;
  489. my $diff = 0;
  490. my $ml;
  491. my $invoice_id;
  492. my $ndx;
  493. foreach $i (1 .. $form->{rowcount}) {
  494. $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
  495. if ($form->{"qty_$i"}) {
  496. $pth->execute($form->{"id_$i"});
  497. $ref = $pth->fetchrow_hashref(NAME_lc);
  498. for (keys %$ref) { $form->{"${_}_$i"} = $ref->{$_} }
  499. $pth->finish;
  500. # project
  501. $project_id = 'NULL';
  502. if ($form->{"projectnumber_$i"}) {
  503. ($null, $project_id) = split /--/, $form->{"projectnumber_$i"};
  504. }
  505. $project_id = $form->{"project_id_$i"} if $form->{"project_id_$i"};
  506. # keep entered selling price
  507. my $fxsellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
  508. my ($dec) = ($fxsellprice =~ /\.(\d+)/);
  509. $dec = length $dec;
  510. my $decimalplaces = ($dec > 2) ? $dec : 2;
  511. # undo discount formatting
  512. $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"})/100;
  513. # deduct discount
  514. $form->{"sellprice_$i"} = $fxsellprice - $form->round_amount($fxsellprice * $form->{"discount_$i"}, $decimalplaces);
  515. # linetotal
  516. my $fxlinetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2);
  517. $amount = $fxlinetotal * $form->{exchangerate};
  518. my $linetotal = $form->round_amount($amount, 2);
  519. $fxdiff += $amount - $linetotal;
  520. @taxaccounts = split / /, $form->{"taxaccounts_$i"};
  521. $ml = 1;
  522. $tax = 0;
  523. $fxtax = 0;
  524. for (0 .. 1) {
  525. $taxrate = 0;
  526. # add tax rates
  527. for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 }
  528. if ($form->{taxincluded}) {
  529. $tax += $amount = $linetotal * ($taxrate / (1 + ($taxrate * $ml)));
  530. $form->{"sellprice_$i"} -= $amount / $form->{"qty_$i"};
  531. $fxtax += $fxlinetotal * ($taxrate / (1 + ($taxrate * $ml)));
  532. } else {
  533. $tax += $amount = $linetotal * $taxrate;
  534. $fxtax += $fxlinetotal * $taxrate;
  535. }
  536. for (@taxaccounts) {
  537. $form->{acc_trans}{$form->{id}}{$_}{amount} += $amount * $form->{"${_}_rate"} / $taxrate if ($form->{"${_}_rate"} * $ml) > 0;
  538. }
  539. $ml = -1;
  540. }
  541. $grossamount = $form->round_amount($linetotal, 2);
  542. if ($form->{taxincluded}) {
  543. $amount = $form->round_amount($tax, 2);
  544. $linetotal -= $form->round_amount($tax - $diff, 2);
  545. $diff = ($amount - $tax);
  546. }
  547. # add linetotal to income
  548. $amount = $form->round_amount($linetotal, 2);
  549. push @{ $form->{acc_trans}{lineitems} }, {
  550. chart_id => $form->{"income_accno_id_$i"},
  551. amount => $amount,
  552. fxgrossamount => $fxlinetotal + $fxtax,
  553. grossamount => $grossamount,
  554. project_id => $project_id };
  555. $ndx = $#{@{$form->{acc_trans}{lineitems}}};
  556. $form->{"sellprice_$i"} = $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate}, $decimalplaces);
  557. if ($form->{"inventory_accno_id_$i"} || $form->{"assembly_$i"}) {
  558. if ($form->{"assembly_$i"}) {
  559. # do not update if assembly consists of all services
  560. $query = qq|SELECT sum(p.inventory_accno_id), p.assembly
  561. FROM parts p
  562. JOIN assembly a ON (a.parts_id = p.id)
  563. WHERE a.id = $form->{"id_$i"}
  564. GROUP BY p.assembly|;
  565. $sth = $dbh->prepare($query);
  566. $sth->execute || $form->dberror($query);
  567. my ($inv, $assembly) = $sth->fetchrow_array;
  568. $sth->finish;
  569. if ($inv || $assembly) {
  570. $form->update_balance($dbh,
  571. "parts",
  572. "onhand",
  573. qq|id = $form->{"id_$i"}|,
  574. $form->{"qty_$i"} * -1) unless $form->{shipped};
  575. }
  576. &process_assembly($dbh, $form, $form->{"id_$i"}, $form->{"qty_$i"}, $project_id);
  577. } else {
  578. $form->update_balance($dbh,
  579. "parts",
  580. "onhand",
  581. qq|id = $form->{"id_$i"}|,
  582. $form->{"qty_$i"} * -1) unless $form->{shipped};
  583. $allocated = &cogs($dbh, $form, $form->{"id_$i"}, $form->{"qty_$i"}, $project_id);
  584. }
  585. }
  586. # save detail record in invoice table
  587. $query = qq|INSERT INTO invoice (description)
  588. VALUES ('$uid')|;
  589. $dbh->do($query) || $form->dberror($query);
  590. $query = qq|SELECT id FROM invoice
  591. WHERE description = '$uid'|;
  592. ($invoice_id) = $dbh->selectrow_array($query);
  593. $query = qq|UPDATE invoice SET
  594. trans_id = $form->{id},
  595. parts_id = $form->{"id_$i"},
  596. description = |.$dbh->quote($form->{"description_$i"}).qq|,
  597. qty = $form->{"qty_$i"},
  598. sellprice = $form->{"sellprice_$i"},
  599. fxsellprice = $fxsellprice,
  600. discount = $form->{"discount_$i"},
  601. allocated = $allocated,
  602. unit = |.$dbh->quote($form->{"unit_$i"}).qq|,
  603. deliverydate = |.$form->dbquote($form->{"deliverydate_$i"}, SQL_DATE).qq|,
  604. project_id = $project_id,
  605. serialnumber = |.$dbh->quote($form->{"serialnumber_$i"}).qq|,
  606. notes = |.$dbh->quote($form->{"notes_$i"}).qq|
  607. WHERE id = $invoice_id|;
  608. $dbh->do($query) || $form->dberror($query);
  609. # add invoice_id
  610. $form->{acc_trans}{lineitems}[$ndx]->{invoice_id} = $invoice_id;
  611. }
  612. }
  613. $form->{paid} = 0;
  614. for $i (1 .. $form->{paidaccounts}) {
  615. $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"});
  616. $form->{paid} += $form->{"paid_$i"};
  617. $form->{datepaid} = $form->{"datepaid_$i"} if ($form->{"paid_$i"});
  618. }
  619. # add lineitems + tax
  620. $amount = 0;
  621. $grossamount = 0;
  622. $fxgrossamount = 0;
  623. for (@{ $form->{acc_trans}{lineitems} }) {
  624. $amount += $_->{amount};
  625. $grossamount += $_->{grossamount};
  626. $fxgrossamount += $_->{fxgrossamount};
  627. }
  628. $invnetamount = $amount;
  629. $amount = 0;
  630. for (split / /, $form->{taxaccounts}) { $amount += $form->{acc_trans}{$form->{id}}{$_}{amount} = $form->round_amount($form->{acc_trans}{$form->{id}}{$_}{amount}, 2) }
  631. $invamount = $invnetamount + $amount;
  632. $diff = 0;
  633. if ($form->{taxincluded}) {
  634. $diff = $form->round_amount($grossamount - $invamount, 2);
  635. $invamount += $diff;
  636. }
  637. $fxdiff = $form->round_amount($fxdiff,2);
  638. $invnetamount += $fxdiff;
  639. $invamount += $fxdiff;
  640. if ($form->round_amount($form->{paid} - $fxgrossamount,2) == 0) {
  641. $form->{paid} = $invamount;
  642. } else {
  643. $form->{paid} = $form->round_amount($form->{paid} * $form->{exchangerate}, 2);
  644. }
  645. foreach $ref (sort { $b->{amount} <=> $a->{amount} } @ { $form->{acc_trans}{lineitems} }) {
  646. $amount = $ref->{amount} + $diff + $fxdiff;
  647. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,
  648. transdate, project_id, invoice_id)
  649. VALUES ($form->{id}, $ref->{chart_id}, $amount,
  650. '$form->{transdate}', $ref->{project_id}, $ref->{invoice_id})|;
  651. $dbh->do($query) || $form->dberror($query);
  652. $diff = 0;
  653. $fxdiff = 0;
  654. }
  655. $form->{receivables} = $invamount * -1;
  656. delete $form->{acc_trans}{lineitems};
  657. # update exchangerate
  658. if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
  659. $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
  660. }
  661. # record receivable
  662. if ($form->{receivables}) {
  663. ($accno) = split /--/, $form->{AR};
  664. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,
  665. transdate)
  666. VALUES ($form->{id},
  667. (SELECT id FROM chart
  668. WHERE accno = '$accno'),
  669. $form->{receivables}, '$form->{transdate}')|;
  670. $dbh->do($query) || $form->dberror($query);
  671. }
  672. foreach my $trans_id (keys %{$form->{acc_trans}}) {
  673. foreach my $accno (keys %{$form->{acc_trans}{$trans_id}}) {
  674. $amount = $form->round_amount($form->{acc_trans}{$trans_id}{$accno}{amount}, 2);
  675. if ($amount) {
  676. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,
  677. transdate)
  678. VALUES ($trans_id, (SELECT id FROM chart
  679. WHERE accno = '$accno'),
  680. $amount, '$form->{transdate}')|;
  681. $dbh->do($query) || $form->dberror($query);
  682. }
  683. }
  684. }
  685. # if there is no amount but a payment record receivable
  686. if ($invamount == 0) {
  687. $form->{receivables} = 1;
  688. }
  689. my $cleared = 0;
  690. # record payments and offsetting AR
  691. for $i (1 .. $form->{paidaccounts}) {
  692. if ($form->{"paid_$i"}) {
  693. my ($accno) = split /--/, $form->{"AR_paid_$i"};
  694. $form->{"datepaid_$i"} = $form->{transdate} unless ($form->{"datepaid_$i"});
  695. $form->{datepaid} = $form->{"datepaid_$i"};
  696. $exchangerate = 0;
  697. if ($form->{currency} eq $form->{defaultcurrency}) {
  698. $form->{"exchangerate_$i"} = 1;
  699. } else {
  700. $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
  701. $form->{"exchangerate_$i"} = ($exchangerate) ? $exchangerate : $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
  702. }
  703. # record AR
  704. $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2);
  705. if ($form->{receivables}) {
  706. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,
  707. transdate)
  708. VALUES ($form->{id}, (SELECT id FROM chart
  709. WHERE accno = '$form->{AR}'),
  710. $amount, '$form->{"datepaid_$i"}')|;
  711. $dbh->do($query) || $form->dberror($query);
  712. }
  713. # record payment
  714. $amount = $form->{"paid_$i"} * -1;
  715. if ($keepcleared) {
  716. $cleared = ($form->{"cleared_$i"}) ? 1 : 0;
  717. }
  718. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate,
  719. source, memo, cleared)
  720. VALUES ($form->{id}, (SELECT id FROM chart
  721. WHERE accno = '$accno'),
  722. $amount, '$form->{"datepaid_$i"}', |
  723. .$dbh->quote($form->{"source_$i"}).qq|, |
  724. .$dbh->quote($form->{"memo_$i"}).qq|, '$cleared')|;
  725. $dbh->do($query) || $form->dberror($query);
  726. # exchangerate difference
  727. $amount = $form->round_amount(($form->round_amount($form->{"paid_$i"} * $form->{"exchangerate_$i"} - $form->{"paid_$i"}, 2)) * -1, 2);
  728. if ($amount) {
  729. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,
  730. transdate, source, fx_transaction, cleared)
  731. VALUES ($form->{id}, (SELECT id FROM chart
  732. WHERE accno = '$accno'),
  733. $amount, '$form->{"datepaid_$i"}', |
  734. .$dbh->quote($form->{"source_$i"}).qq|, '1', '$cleared')|;
  735. $dbh->do($query) || $form->dberror($query);
  736. }
  737. # gain/loss
  738. $amount = $form->round_amount(($form->round_amount($form->{"paid_$i"} * $form->{exchangerate},2) - $form->round_amount($form->{"paid_$i"} * $form->{"exchangerate_$i"},2)) * -1, 2);
  739. if ($amount) {
  740. my $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id;
  741. $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount,
  742. transdate, fx_transaction, cleared)
  743. VALUES ($form->{id}, $accno_id,
  744. $amount, '$form->{"datepaid_$i"}', '1', '$cleared')|;
  745. $dbh->do($query) || $form->dberror($query);
  746. }
  747. # update exchange rate
  748. if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
  749. $form->update_exchangerate($dbh, $form->{currency}, $form->{"datepaid_$i"}, $form->{"exchangerate_$i"}, 0);
  750. }
  751. }
  752. }
  753. # set values which could be empty to 0
  754. $form->{terms} *= 1;
  755. $form->{taxincluded} *= 1;
  756. # if this is from a till
  757. my $till = ($form->{till}) ? qq|'$form->{till}'| : "NULL";
  758. $form->{invnumber} = $form->update_defaults($myconfig, "sinumber", $dbh) unless $form->{invnumber};
  759. # save AR record
  760. $query = qq|UPDATE ar set
  761. invnumber = |.$dbh->quote($form->{invnumber}).qq|,
  762. ordnumber = |.$dbh->quote($form->{ordnumber}).qq|,
  763. quonumber = |.$dbh->quote($form->{quonumber}).qq|,
  764. transdate = '$form->{transdate}',
  765. customer_id = $form->{customer_id},
  766. amount = $invamount,
  767. netamount = $invnetamount,
  768. paid = $form->{paid},
  769. datepaid = |.$form->dbquote($form->{datepaid}, SQL_DATE).qq|,
  770. duedate = |.$form->dbquote($form->{duedate}, SQL_DATE).qq|,
  771. invoice = '1',
  772. shippingpoint = |.$dbh->quote($form->{shippingpoint}).qq|,
  773. shipvia = |.$dbh->quote($form->{shipvia}).qq|,
  774. terms = $form->{terms},
  775. notes = |.$dbh->quote($form->{notes}).qq|,
  776. intnotes = |.$dbh->quote($form->{intnotes}).qq|,
  777. taxincluded = '$form->{taxincluded}',
  778. curr = '$form->{currency}',
  779. department_id = $form->{department_id},
  780. employee_id = $form->{employee_id},
  781. till = $till,
  782. language_code = '$form->{language_code}',
  783. ponumber = |.$dbh->quote($form->{ponumber}).qq|
  784. WHERE id = $form->{id}
  785. |;
  786. $dbh->do($query) || $form->dberror($query);
  787. # add shipto
  788. $form->{name} = $form->{customer};
  789. $form->{name} =~ s/--$form->{customer_id}//;
  790. $form->add_shipto($dbh, $form->{id});
  791. # save printed, emailed and queued
  792. $form->save_status($dbh);
  793. my %audittrail = ( tablename => 'ar',
  794. reference => $form->{invnumber},
  795. formname => $form->{type},
  796. action => 'posted',
  797. id => $form->{id} );
  798. $form->audittrail($dbh, "", \%audittrail);
  799. $form->save_recurring($dbh, $myconfig);
  800. my $rc = $dbh->commit;
  801. $dbh->disconnect;
  802. $rc;
  803. }
  804. sub process_assembly {
  805. my ($dbh, $form, $id, $totalqty, $project_id) = @_;
  806. my $query = qq|SELECT a.parts_id, a.qty, p.assembly,
  807. p.partnumber, p.description, p.unit,
  808. p.inventory_accno_id, p.income_accno_id,
  809. p.expense_accno_id
  810. FROM assembly a
  811. JOIN parts p ON (a.parts_id = p.id)
  812. WHERE a.id = $id|;
  813. my $sth = $dbh->prepare($query);
  814. $sth->execute || $form->dberror($query);
  815. my $allocated;
  816. while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
  817. $allocated = 0;
  818. $ref->{inventory_accno_id} *= 1;
  819. $ref->{expense_accno_id} *= 1;
  820. # multiply by number of assemblies
  821. $ref->{qty} *= $totalqty;
  822. if ($ref->{assembly}) {
  823. &process_assembly($dbh, $form, $ref->{parts_id}, $ref->{qty}, $project_id);
  824. next;
  825. } else {
  826. if ($ref->{inventory_accno_id}) {
  827. $allocated = &cogs($dbh, $form, $ref->{parts_id}, $ref->{qty}, $project_id);
  828. }
  829. }
  830. # save detail record for individual assembly item in invoice table
  831. $query = qq|INSERT INTO invoice (trans_id, description, parts_id, qty,
  832. sellprice, fxsellprice, allocated, assemblyitem, unit)
  833. VALUES
  834. ($form->{id}, |
  835. .$dbh->quote($ref->{description}).qq|,
  836. $ref->{parts_id}, $ref->{qty}, 0, 0, $allocated, 't', |
  837. .$dbh->quote($ref->{unit}).qq|)|;
  838. $dbh->do($query) || $form->dberror($query);
  839. }
  840. $sth->finish;
  841. }
  842. sub cogs {
  843. my ($dbh, $form, $id, $totalqty, $project_id) = @_;
  844. my $query = qq|SELECT i.id, i.trans_id, i.qty, i.allocated, i.sellprice,
  845. i.fxsellprice, p.inventory_accno_id, p.expense_accno_id
  846. FROM invoice i, parts p
  847. WHERE i.parts_id = p.id
  848. AND i.parts_id = $id
  849. AND (i.qty + i.allocated) < 0
  850. ORDER BY trans_id|;
  851. my $sth = $dbh->prepare($query);
  852. $sth->execute || $form->dberror($query);
  853. my $allocated = 0;
  854. my $qty;
  855. while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
  856. if (($qty = (($ref->{qty} * -1) - $ref->{allocated})) > $totalqty) {
  857. $qty = $totalqty;
  858. }
  859. $form->update_balance($dbh,
  860. "invoice",
  861. "allocated",
  862. qq|id = $ref->{id}|,
  863. $qty);
  864. # total expenses and inventory
  865. # sellprice is the cost of the item
  866. my $linetotal = $form->round_amount($ref->{sellprice} * $qty, 2);
  867. # add expense
  868. push @{ $form->{acc_trans}{lineitems} }, {
  869. chart_id => $ref->{expense_accno_id},
  870. amount => $linetotal * -1,
  871. project_id => $project_id,
  872. invoice_id => $ref->{id} };
  873. # deduct inventory
  874. push @{ $form->{acc_trans}{lineitems} }, {
  875. chart_id => $ref->{inventory_accno_id},
  876. amount => $linetotal,
  877. project_id => $project_id,
  878. invoice_id => $ref->{id} };
  879. # add allocated
  880. $allocated += -$qty;
  881. last if (($totalqty -= $qty) <= 0);
  882. }
  883. $sth->finish;
  884. $allocated;
  885. }
  886. sub reverse_invoice {
  887. my ($dbh, $form) = @_;
  888. my $query = qq|SELECT id FROM ar
  889. WHERE id = $form->{id}|;
  890. my ($id) = $dbh->selectrow_array($query);
  891. return unless $id;
  892. # reverse inventory items
  893. my $query = qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly,
  894. p.inventory_accno_id
  895. FROM invoice i
  896. JOIN parts p ON (i.parts_id = p.id)
  897. WHERE i.trans_id = $form->{id}|;
  898. my $sth = $dbh->prepare($query);
  899. $sth->execute || $form->dberror($query);
  900. while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
  901. if ($ref->{inventory_accno_id} || $ref->{assembly}) {
  902. # if the invoice item is not an assemblyitem adjust parts onhand
  903. if (!$ref->{assemblyitem}) {
  904. # adjust onhand in parts table
  905. $form->update_balance($dbh,
  906. "parts",
  907. "onhand",
  908. qq|id = $ref->{parts_id}|,
  909. $ref->{qty});
  910. }
  911. # loop if it is an assembly
  912. next if ($ref->{assembly});
  913. # de-allocated purchases
  914. $query = qq|SELECT id, trans_id, allocated
  915. FROM invoice
  916. WHERE parts_id = $ref->{parts_id}
  917. AND allocated > 0
  918. ORDER BY trans_id DESC|;
  919. my $sth = $dbh->prepare($query);
  920. $sth->execute || $form->dberror($query);
  921. while (my $inhref = $sth->fetchrow_hashref(NAME_lc)) {
  922. $qty = $ref->{qty};
  923. if (($ref->{qty} - $inhref->{allocated}) > 0) {
  924. $qty = $inhref->{allocated};
  925. }
  926. # update invoice
  927. $form->update_balance($dbh,
  928. "invoice",
  929. "allocated",
  930. qq|id = $inhref->{id}|,
  931. $qty * -1);
  932. last if (($ref->{qty} -= $qty) <= 0);
  933. }
  934. $sth->finish;
  935. }
  936. }
  937. $sth->finish;
  938. # delete acc_trans
  939. $query = qq|DELETE FROM acc_trans
  940. WHERE trans_id = $form->{id}|;
  941. $dbh->do($query) || $form->dberror($query);
  942. # delete invoice entries
  943. $query = qq|DELETE FROM invoice
  944. WHERE trans_id = $form->{id}|;
  945. $dbh->do($query) || $form->dberror($query);
  946. $query = qq|DELETE FROM shipto
  947. WHERE trans_id = $form->{id}|;
  948. $dbh->do($query) || $form->dberror($query);
  949. $dbh->commit;
  950. }
  951. sub delete_invoice {
  952. my ($self, $myconfig, $form, $spool) = @_;
  953. # connect to database
  954. my $dbh = $form->dbconnect_noauto($myconfig);
  955. &reverse_invoice($dbh, $form);
  956. my %audittrail = ( tablename => 'ar',
  957. reference => $form->{invnumber},
  958. formname => $form->{type},
  959. action => 'deleted',
  960. id => $form->{id} );
  961. $form->audittrail($dbh, "", \%audittrail);
  962. # delete AR record
  963. my $query = qq|DELETE FROM ar
  964. WHERE id = $form->{id}|;
  965. $dbh->do($query) || $form->dberror($query);
  966. # delete spool files
  967. $query = qq|SELECT spoolfile FROM status
  968. WHERE trans_id = $form->{id}
  969. AND spoolfile IS NOT NULL|;
  970. my $sth = $dbh->prepare($query);
  971. $sth->execute || $form->dberror($query);
  972. my $spoolfile;
  973. my @spoolfiles = ();
  974. while (($spoolfile) = $sth->fetchrow_array) {
  975. push @spoolfiles, $spoolfile;
  976. }
  977. $sth->finish;
  978. # delete status entries
  979. $query = qq|DELETE FROM status
  980. WHERE trans_id = $form->{id}|;
  981. $dbh->do($query) || $form->dberror($query);
  982. my $rc = $dbh->commit;
  983. if ($rc) {
  984. foreach $spoolfile (@spoolfiles) {
  985. unlink "$spool/$spoolfile" if $spoolfile;
  986. }
  987. }
  988. $dbh->disconnect;
  989. $rc;
  990. }
  991. sub retrieve_invoice {
  992. my ($self, $myconfig, $form) = @_;
  993. # connect to database
  994. my $dbh = $form->dbconnect_noauto($myconfig);
  995. my $query;
  996. if ($form->{id}) {
  997. # get default accounts and last invoice number
  998. $query = qq|SELECT d.curr AS currencies
  999. FROM defaults d|;
  1000. } else {
  1001. $query = qq|SELECT d.curr AS currencies, current_date AS transdate
  1002. FROM defaults d|;
  1003. }
  1004. my $sth = $dbh->prepare($query);
  1005. $sth->execute || $form->dberror($query);
  1006. my $ref = $sth->fetchrow_hashref(NAME_lc);
  1007. for (keys %$ref) { $form->{$_} = $ref->{$_} }
  1008. $sth->finish;
  1009. if ($form->{id}) {
  1010. # retrieve invoice
  1011. $query = qq|SELECT a.invnumber, a.ordnumber, a.quonumber,
  1012. a.transdate, a.paid,
  1013. a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes,
  1014. a.duedate, a.taxincluded, a.curr AS currency,
  1015. a.employee_id, e.name AS employee, a.till, a.customer_id,
  1016. a.language_code, a.ponumber
  1017. FROM ar a
  1018. LEFT JOIN employee e ON (e.id = a.employee_id)
  1019. WHERE a.id = $form->{id}|;
  1020. $sth = $dbh->prepare($query);
  1021. $sth->execute || $form->dberror($query);
  1022. $ref = $sth->fetchrow_hashref(NAME_lc);
  1023. for (keys %$ref) { $form->{$_} = $ref->{$_} }
  1024. $sth->finish;
  1025. # get shipto
  1026. $query = qq|SELECT * FROM shipto
  1027. WHERE trans_id = $form->{id}|;
  1028. $sth = $dbh->prepare($query);
  1029. $sth->execute || $form->dberror($query);
  1030. $ref = $sth->fetchrow_hashref(NAME_lc);
  1031. for (keys %$ref) { $form->{$_} = $ref->{$_} }
  1032. $sth->finish;
  1033. # retrieve individual items
  1034. $query = qq|SELECT i.description, i.qty, i.fxsellprice, i.sellprice,
  1035. i.discount, i.parts_id AS id, i.unit, i.deliverydate,
  1036. i.project_id, pr.projectnumber, i.serialnumber, i.notes,
  1037. p.partnumber, p.assembly, p.bin,
  1038. pg.partsgroup, p.partsgroup_id, p.partnumber AS sku,
  1039. p.listprice, p.lastcost, p.weight, p.onhand,
  1040. p.inventory_accno_id, p.income_accno_id, p.expense_accno_id,
  1041. t.description AS partsgrouptranslation
  1042. FROM invoice i
  1043. JOIN parts p ON (i.parts_id = p.id)
  1044. LEFT JOIN project pr ON (i.project_id = pr.id)
  1045. LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
  1046. LEFT JOIN translation t ON (t.trans_id = p.partsgroup_id AND t.language_code = '$form->{language_code}')
  1047. WHERE i.trans_id = $form->{id}
  1048. AND NOT i.assemblyitem = '1'
  1049. ORDER BY i.id|;
  1050. $sth = $dbh->prepare($query);
  1051. $sth->execute || $form->dberror($query);
  1052. # foreign currency
  1053. &exchangerate_defaults($dbh, $form);
  1054. # query for price matrix
  1055. my $pmh = &price_matrix_query($dbh, $form);
  1056. # taxes
  1057. $query = qq|SELECT c.accno
  1058. FROM chart c
  1059. JOIN partstax pt ON (pt.chart_id = c.id)
  1060. WHERE pt.parts_id = ?|;
  1061. my $tth = $dbh->prepare($query) || $form->dberror($query);
  1062. my $taxrate;
  1063. my $ptref;
  1064. while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
  1065. my ($dec) = ($ref->{fxsellprice} =~ /\.(\d+)/);
  1066. $dec = length $dec;
  1067. my $decimalplaces = ($dec > 2) ? $dec : 2;
  1068. $tth->execute($ref->{id});
  1069. $ref->{taxaccounts} = "";
  1070. $taxrate = 0;
  1071. while ($ptref = $tth->fetchrow_hashref(NAME_lc)) {
  1072. $ref->{taxaccounts} .= "$ptref->{accno} ";
  1073. $taxrate += $form->{"$ptref->{accno}_rate"};
  1074. }
  1075. $tth->finish;
  1076. chop $ref->{taxaccounts};
  1077. # price matrix
  1078. $ref->{sellprice} = ($ref->{fxsellprice} * $form->{$form->{currency}});
  1079. &price_matrix($pmh, $ref, $form->{transdate}, $decimalplaces, $form, $myconfig);
  1080. $ref->{sellprice} = $ref->{fxsellprice};
  1081. $ref->{partsgroup} = $ref->{partsgrouptranslation} if $ref->{partsgrouptranslation};
  1082. push @{ $form->{invoice_details} }, $ref;
  1083. }
  1084. $sth->finish;
  1085. }
  1086. my $rc = $dbh->commit;
  1087. $dbh->disconnect;
  1088. $rc;
  1089. }
  1090. sub retrieve_item {
  1091. my ($self, $myconfig, $form) = @_;
  1092. # connect to database
  1093. my $dbh = $form->dbconnect($myconfig);
  1094. my $i = $form->{rowcount};
  1095. my $null;
  1096. my $var;
  1097. my $where = "WHERE p.obsolete = '0' AND NOT p.income_accno_id IS NULL";
  1098. if ($form->{"partnumber_$i"} ne "") {
  1099. $var = $form->like(lc $form->{"partnumber_$i"});
  1100. $where .= " AND lower(p.partnumber) LIKE '$var'";
  1101. }
  1102. if ($form->{"description_$i"} ne "") {
  1103. $var = $form->like(lc $form->{"description_$i"});
  1104. if ($form->{language_code} ne "") {
  1105. $where .= " AND lower(t1.description) LIKE '$var'";
  1106. } else {
  1107. $where .= " AND lower(p.description) LIKE '$var'";
  1108. }
  1109. }
  1110. if ($form->{"partsgroup_$i"} ne "") {
  1111. ($null, $var) = split /--/, $form->{"partsgroup_$i"};
  1112. $var *= 1;
  1113. if ($var == 0) {
  1114. # search by partsgroup, this is for the POS
  1115. $where .= qq| AND pg.partsgroup = '$form->{"partsgroup_$i"}'|;
  1116. } else {
  1117. $where .= qq| AND p.partsgroup_id = $var|;
  1118. }
  1119. }
  1120. if ($form->{"description_$i"} ne "") {
  1121. $where .= " ORDER BY 3";
  1122. } else {
  1123. $where .= " ORDER BY 2";
  1124. }
  1125. my $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice,
  1126. p.listprice, p.lastcost,
  1127. p.unit, p.assembly, p.bin, p.onhand, p.notes,
  1128. p.inventory_accno_id, p.income_accno_id, p.expense_accno_id,
  1129. pg.partsgroup, p.partsgroup_id, p.partnumber AS sku,
  1130. p.weight,
  1131. t1.description AS translation,
  1132. t2.description AS grouptranslation
  1133. FROM parts p
  1134. LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
  1135. LEFT JOIN translation t1 ON (t1.trans_id = p.id AND t1.language_code = '$form->{language_code}')
  1136. LEFT JOIN translation t2 ON (t2.trans_id = p.partsgroup_id AND t2.language_code = '$form->{language_code}')
  1137. $where|;
  1138. my $sth = $dbh->prepare($query);
  1139. $sth->execute || $form->dberror($query);
  1140. my $ref;
  1141. my $ptref;
  1142. # setup exchange rates
  1143. &exchangerate_defaults($dbh, $form);
  1144. # taxes
  1145. $query = qq|SELECT c.accno
  1146. FROM chart c
  1147. JOIN partstax pt ON (c.id = pt.chart_id)
  1148. WHERE pt.parts_id = ?|;
  1149. my $tth = $dbh->prepare($query) || $form->dberror($query);
  1150. # price matrix
  1151. my $pmh = &price_matrix_query($dbh, $form);
  1152. my $transdate = $form->datetonum($myconfig, $form->{transdate});
  1153. while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
  1154. my ($dec) = ($ref->{sellprice} =~ /\.(\d+)/);
  1155. $dec = length $dec;
  1156. my $decimalplaces = ($dec > 2) ? $dec : 2;
  1157. # get taxes for part
  1158. $tth->execute($ref->{id});
  1159. $ref->{taxaccounts} = "";
  1160. while ($ptref = $tth->fetchrow_hashref(NAME_lc)) {
  1161. $ref->{taxaccounts} .= "$ptref->{accno} ";
  1162. }
  1163. $tth->finish;
  1164. chop $ref->{taxaccounts};
  1165. # get matrix
  1166. &price_matrix($pmh, $ref, $transdate, $decimalplaces, $form, $myconfig);
  1167. $ref->{description} = $ref->{translation} if $ref->{translation};
  1168. $ref->{partsgroup} = $ref->{grouptranslation} if $ref->{grouptranslation};
  1169. push @{ $form->{item_list} }, $ref;
  1170. }
  1171. $sth->finish;
  1172. $dbh->disconnect;
  1173. }
  1174. sub price_matrix_query {
  1175. my ($dbh, $form) = @_;
  1176. my $query = qq|SELECT p.id AS parts_id, 0 AS customer_id, 0 AS pricegroup_id,
  1177. 0 AS pricebreak, p.sellprice, NULL AS validfrom, NULL AS validto,
  1178. '$form->{defaultcurrency}' AS curr, '' AS pricegroup
  1179. FROM parts p
  1180. WHERE p.id = ?
  1181. UNION
  1182. SELECT p.*, g.pricegroup
  1183. FROM partscustomer p
  1184. LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id)
  1185. WHERE p.parts_id = ?
  1186. AND p.customer_id = $form->{customer_id}
  1187. UNION
  1188. SELECT p.*, g.pricegroup
  1189. FROM partscustomer p
  1190. LEFT JOIN pricegroup g ON (g.id = p.pricegroup_id)
  1191. JOIN customer c ON (c.pricegroup_id = g.id)
  1192. WHERE p.parts_id = ?
  1193. AND c.id = $form->{customer_id}
  1194. UNION
  1195. SELECT p.*, '' AS pricegroup
  1196. FROM partscustomer p
  1197. WHERE p.customer_id = 0
  1198. AND p.pricegroup_id = 0
  1199. AND p.parts_id = ?
  1200. ORDER BY customer_id DESC, pricegroup_id DESC, pricebreak
  1201. |;
  1202. my $sth = $dbh->prepare($query) || $form->dberror($query);
  1203. $sth;
  1204. }
  1205. sub price_matrix {
  1206. my ($pmh, $ref, $transdate, $decimalplaces, $form, $myconfig) = @_;
  1207. $pmh->execute($ref->{id}, $ref->{id}, $ref->{id}, $ref->{id});
  1208. $ref->{pricematrix} = "";
  1209. my $customerprice;
  1210. my $pricegroupprice;
  1211. my $sellprice;
  1212. my $baseprice;
  1213. my $mref;
  1214. my %p = ();
  1215. my $i = 0;
  1216. while ($mref = $pmh->fetchrow_hashref(NAME_lc)) {
  1217. # check date
  1218. if ($mref->{validfrom}) {
  1219. next if $transdate < $form->datetonum($myconfig, $mref->{validfrom});
  1220. }
  1221. if ($mref->{validto}) {
  1222. next if $transdate > $form->datetonum($myconfig, $mref->{validto});
  1223. }
  1224. # convert price
  1225. $sellprice = $form->round_amount($mref->{sellprice} * $form->{$mref->{curr}}, $decimalplaces);
  1226. $mref->{pricebreak} *= 1;
  1227. if ($mref->{customer_id}) {
  1228. $p{$mref->{pricebreak}} = $sellprice;
  1229. $customerprice = 1;
  1230. }
  1231. if ($mref->{pricegroup_id}) {
  1232. if (!$customerprice) {
  1233. $p{$mref->{pricebreak}} = $sellprice;
  1234. $pricegroupprice = 1;
  1235. }
  1236. }
  1237. if (!$customerprice && !$pricegroupprice) {
  1238. $p{$mref->{pricebreak}} = $sellprice;
  1239. }
  1240. if (($mref->{pricebreak} + $mref->{customer_id} + $mref->{pricegroup_id}) == 0) {
  1241. $baseprice = $sellprice;
  1242. }
  1243. $i++;
  1244. }
  1245. $pmh->finish;
  1246. if (! exists $p{0}) {
  1247. $p{0} = $baseprice;
  1248. }
  1249. if ($i > 1) {
  1250. $ref->{sellprice} = $p{0};
  1251. for (sort { $a <=> $b } keys %p) { $ref->{pricematrix} .= "${_}:$p{$_} " }
  1252. } else {
  1253. $ref->{sellprice} = $form->round_amount($p{0} * (1 - $form->{tradediscount}), $decimalplaces);
  1254. $ref->{pricematrix} = "0:$ref->{sellprice} " if $ref->{sellprice};
  1255. }
  1256. chop $ref->{pricematrix};
  1257. }
  1258. sub exchangerate_defaults {
  1259. my ($dbh, $form) = @_;
  1260. my $var;
  1261. # get default currencies
  1262. my $query = qq|SELECT substr(curr,1,3), curr FROM defaults|;
  1263. my $eth = $dbh->prepare($query) || $form->dberror($query);
  1264. $eth->execute;
  1265. ($form->{defaultcurrency}, $form->{currencies}) = $eth->fetchrow_array;
  1266. $eth->finish;
  1267. $query = qq|SELECT buy
  1268. FROM exchangerate
  1269. WHERE curr = ?
  1270. AND transdate = ?|;
  1271. my $eth1 = $dbh->prepare($query) || $form->dberror($query);
  1272. $query = qq~SELECT max(transdate || ' ' || buy || ' ' || curr)
  1273. FROM exchangerate
  1274. WHERE curr = ?~;
  1275. my $eth2 = $dbh->prepare($query) || $form->dberror($query);
  1276. # get exchange rates for transdate or max
  1277. foreach $var (split /:/, substr($form->{currencies},4)) {
  1278. $eth1->execute($var, $form->{transdate});
  1279. ($form->{$var}) = $eth1->fetchrow_array;
  1280. if (! $form->{$var} ) {
  1281. $eth2->execute($var);
  1282. ($form->{$var}) = $eth2->fetchrow_array;
  1283. ($null, $form->{$var}) = split / /, $form->{$var};
  1284. $form->{$var} = 1 unless $form->{$var};
  1285. $eth2->finish;
  1286. }
  1287. $eth1->finish;
  1288. }
  1289. $form->{$form->{currency}} = $form->{exchangerate} if $form->{exchangerate};
  1290. $form->{$form->{currency}} ||= 1;
  1291. $form->{$form->{defaultcurrency}} = 1;
  1292. }
  1293. 1;