Child sales document preparation moved to cart class.
[fa-stable.git] / sales / customer_invoice.php
1 <?php
2 /**********************************************************************
3     Copyright (C) FrontAccounting, LLC.
4         Released under the terms of the GNU General Public License, GPL, 
5         as published by the Free Software Foundation, either version 3 
6         of the License, or (at your option) any later version.
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
10     See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
11 ***********************************************************************/
12 //---------------------------------------------------------------------------
13 //
14 //      Entry/Modify Sales Invoice against single delivery
15 //      Entry/Modify Batch Sales Invoice against batch of deliveries
16 //
17 $page_security = 'SA_SALESINVOICE';
18 $path_to_root = "..";
19 include_once($path_to_root . "/sales/includes/cart_class.inc");
20 include_once($path_to_root . "/includes/session.inc");
21 include_once($path_to_root . "/includes/data_checks.inc");
22 include_once($path_to_root . "/includes/manufacturing.inc");
23 include_once($path_to_root . "/sales/includes/sales_db.inc");
24 include_once($path_to_root . "/sales/includes/sales_ui.inc");
25 include_once($path_to_root . "/reporting/includes/reporting.inc");
26 include_once($path_to_root . "/taxes/tax_calc.inc");
27
28 $js = "";
29 if ($use_popup_windows) {
30         $js .= get_js_open_window(900, 500);
31 }
32 if ($use_date_picker) {
33         $js .= get_js_date_picker();
34 }
35
36 if (isset($_GET['ModifyInvoice'])) {
37         $_SESSION['page_title'] = sprintf(_("Modifying Sales Invoice # %d.") ,$_GET['ModifyInvoice']);
38         $help_context = "Modifying Sales Invoice";
39 } elseif (isset($_GET['DeliveryNumber'])) {
40         $_SESSION['page_title'] = _($help_context = "Issue an Invoice for Delivery Note");
41 } elseif (isset($_GET['BatchInvoice'])) {
42         $_SESSION['page_title'] = _($help_context = "Issue Batch Invoice for Delivery Notes");
43 }
44
45 page($_SESSION['page_title'], false, false, "", $js);
46
47 //-----------------------------------------------------------------------------
48 check_edit_conflicts();
49
50 if (isset($_GET['AddedID'])) {
51
52         $invoice_no = $_GET['AddedID'];
53         $trans_type = ST_SALESINVOICE;
54
55         display_notification(_("Selected deliveries has been processed"), true);
56
57         display_note(get_customer_trans_view_str($trans_type, $invoice_no, _("&View This Invoice")), 0, 1);
58
59         display_note(print_document_link($invoice_no."-".$trans_type, _("&Print This Invoice"), true, ST_SALESINVOICE));
60         display_note(print_document_link($invoice_no."-".$trans_type, _("&Email This Invoice"), true, ST_SALESINVOICE, false, "printlink", "", 1),1);
61
62         display_note(get_gl_view_str($trans_type, $invoice_no, _("View the GL &Journal Entries for this Invoice")),1);
63
64         hyperlink_params("$path_to_root/sales/inquiry/sales_deliveries_view.php", _("Select Another &Delivery For Invoicing"), "OutstandingOnly=1");
65
66         display_footer_exit();
67
68 } elseif (isset($_GET['UpdatedID']))  {
69
70         $invoice_no = $_GET['UpdatedID'];
71         $trans_type = ST_SALESINVOICE;
72
73         display_notification_centered(sprintf(_('Sales Invoice # %d has been updated.'),$invoice_no));
74
75         display_note(get_trans_view_str(ST_SALESINVOICE, $invoice_no, _("&View This Invoice")));
76         echo '<br>';
77         display_note(print_document_link($invoice_no."-".$trans_type, _("&Print This Invoice"), true, ST_SALESINVOICE));
78         display_note(print_document_link($invoice_no."-".$trans_type, _("&Email This Invoice"), true, ST_SALESINVOICE, false, "printlink", "", 1),1);
79
80         hyperlink_no_params($path_to_root . "/sales/inquiry/customer_inquiry.php", _("Select Another &Invoice to Modify"));
81
82         display_footer_exit();
83
84 } elseif (isset($_GET['RemoveDN'])) {
85
86         for($line_no = 0; $line_no < count($_SESSION['Items']->line_items); $line_no++) {
87                 $line = &$_SESSION['Items']->line_items[$line_no];
88                 if ($line->src_no == $_GET['RemoveDN']) {
89                         $line->quantity = $line->qty_done;
90                         $line->qty_dispatched=0;
91                 }
92         }
93         unset($line);
94
95     // Remove also src_doc delivery note
96     $sources = &$_SESSION['Items']->src_docs;
97     unset($sources[$_GET['RemoveDN']]);
98 }
99
100 //-----------------------------------------------------------------------------
101
102 if ( (isset($_GET['DeliveryNumber']) && ($_GET['DeliveryNumber'] > 0) )
103 || isset($_GET['BatchInvoice'])) {
104
105         processing_start();
106
107         if (isset($_GET['BatchInvoice'])) {
108                 $src = $_SESSION['DeliveryBatch'];
109                 unset($_SESSION['DeliveryBatch']);
110         } else {
111                 $src = array($_GET['DeliveryNumber']);
112         }
113
114         /*read in all the selected deliveries into the Items cart  */
115         $dn = new Cart(ST_CUSTDELIVERY, $src, true);
116
117         if ($dn->count_items() == 0) {
118                 hyperlink_params($path_to_root . "/sales/inquiry/sales_deliveries_view.php",
119                         _("Select a different delivery to invoice"), "OutstandingOnly=1");
120                 die ("<br><b>" . _("There are no delivered items with a quantity left to invoice. There is nothing left to invoice.") . "</b>");
121         }
122
123         $_SESSION['Items'] = $dn;
124         copy_from_cart();
125
126 } elseif (isset($_GET['ModifyInvoice']) && $_GET['ModifyInvoice'] > 0) {
127
128         if ( get_sales_parent_numbers(ST_SALESINVOICE, $_GET['ModifyInvoice']) == 0) { // 1.xx compatibility hack
129                 echo"<center><br><b>" . _("There are no delivery notes for this invoice.<br>
130                 Most likely this invoice was created in Front Accounting version prior to 2.0
131                 and therefore can not be modified.") . "</b></center>";
132                 display_footer_exit();
133         }
134         processing_start();
135         $_SESSION['Items'] = new Cart(ST_SALESINVOICE, $_GET['ModifyInvoice']);
136
137         if ($_SESSION['Items']->count_items() == 0) {
138                 echo"<center><br><b>" . _("All quantities on this invoice has been credited. There is nothing to modify on this invoice") . "</b></center>";
139                 display_footer_exit();
140         }
141         copy_from_cart();
142 } elseif (!processing_active()) {
143         /* This page can only be called with a delivery for invoicing or invoice no for edit */
144         display_error(_("This page can only be opened after delivery selection. Please select delivery to invoicing first."));
145
146         hyperlink_no_params("$path_to_root/sales/inquiry/sales_deliveries_view.php", _("Select Delivery to Invoice"));
147
148         end_page();
149         exit;
150 } elseif (!check_quantities()) {
151         display_error(_("Selected quantity cannot be less than quantity credited nor more than quantity not invoiced yet."));
152 }
153 if (isset($_POST['Update'])) {
154         $Ajax->activate('Items');
155 }
156 if (isset($_POST['_InvoiceDate_changed'])) {
157         $_POST['due_date'] = get_invoice_duedate($_SESSION['Items']->payment, $_POST['InvoiceDate']);
158         $Ajax->activate('due_date');
159 }
160 if (list_updated('payment')) {
161         $_SESSION['Items']->payment = get_post('payment');
162         $_POST['due_date'] = get_invoice_duedate($_SESSION['Items']->payment, $_POST['InvoiceDate']);
163         $Ajax->activate('due_date');
164 }
165
166 //-----------------------------------------------------------------------------
167 function check_quantities()
168 {
169         $ok =1;
170         foreach ($_SESSION['Items']->line_items as $line_no=>$itm) {
171                 if (isset($_POST['Line'.$line_no])) {
172                         if($_SESSION['Items']->trans_no) {
173                                 $min = $itm->qty_done;
174                                 $max = $itm->quantity;
175                         } else {
176                                 $min = 0;
177                                 $max = $itm->quantity - $itm->qty_done;
178                         }
179                         if (check_num('Line'.$line_no, $min, $max)) {
180                                 $_SESSION['Items']->line_items[$line_no]->qty_dispatched =
181                                     input_num('Line'.$line_no);
182                         }
183                         else {
184                                 $ok = 0;
185                         }
186                                 
187                 }
188
189                 if (isset($_POST['Line'.$line_no.'Desc'])) {
190                         $line_desc = $_POST['Line'.$line_no.'Desc'];
191                         if (strlen($line_desc) > 0) {
192                                 $_SESSION['Items']->line_items[$line_no]->item_description = $line_desc;
193                         }
194                 }
195         }
196  return $ok;
197 }
198
199 function set_delivery_shipping_sum($delivery_notes) 
200 {
201     
202     $shipping = 0;
203     
204     foreach($delivery_notes as $delivery_num) 
205     {
206         $myrow = get_customer_trans($delivery_num, 13);
207         //$branch = get_branch($myrow["branch_code"]);
208         //$sales_order = get_sales_order_header($myrow["order_"]);
209         
210         //$shipping += $sales_order['freight_cost'];
211         $shipping += $myrow['ov_freight'];
212     }
213     $_POST['ChargeFreightCost'] = price_format($shipping);
214 }
215
216
217 function copy_to_cart()
218 {
219         $cart = &$_SESSION['Items'];
220         $cart->ship_via = $_POST['ship_via'];
221         $cart->freight_cost = input_num('ChargeFreightCost');
222         $cart->document_date =  $_POST['InvoiceDate'];
223         $cart->due_date =  $_POST['due_date'];
224         if ($cart->pos['cash_sale'] || $cart->pos['credit_sale']) {
225                 $cart->payment = $_POST['payment'];
226                 $cart->payment_terms = get_payment_terms($_POST['payment']);
227         }
228         $cart->Comments = $_POST['Comments'];
229         if ($_SESSION['Items']->trans_no == 0)
230                 $cart->reference = $_POST['ref'];
231
232 }
233 //-----------------------------------------------------------------------------
234
235 function copy_from_cart()
236 {
237         $cart = &$_SESSION['Items'];
238         $_POST['ship_via'] = $cart->ship_via;
239         $_POST['ChargeFreightCost'] = price_format($cart->freight_cost);
240         $_POST['InvoiceDate']= $cart->document_date;
241         $_POST['due_date'] = $cart->due_date;
242         $_POST['Comments']= $cart->Comments;
243         $_POST['cart_id'] = $cart->cart_id;
244         $_POST['ref'] = $cart->reference;
245         $_POST['payment'] = $cart->payment;
246 }
247
248 //-----------------------------------------------------------------------------
249
250 function check_data()
251 {
252         global $Refs;
253
254         if (!isset($_POST['InvoiceDate']) || !is_date($_POST['InvoiceDate'])) {
255                 display_error(_("The entered invoice date is invalid."));
256                 set_focus('InvoiceDate');
257                 return false;
258         }
259
260         if (!is_date_in_fiscalyear($_POST['InvoiceDate'])) {
261                 display_error(_("The entered invoice date is not in fiscal year."));
262                 set_focus('InvoiceDate');
263                 return false;
264         }
265
266         if (!isset($_POST['due_date']) || !is_date($_POST['due_date'])) {
267                 display_error(_("The entered invoice due date is invalid."));
268                 set_focus('due_date');
269                 return false;
270         }
271
272         if ($_SESSION['Items']->trans_no == 0) {
273                 if (!$Refs->is_valid($_POST['ref'])) {
274                         display_error(_("You must enter a reference."));
275                         set_focus('ref');
276                         return false;
277                 }
278
279                 if (!is_new_reference($_POST['ref'], 10)) {
280                         display_error(_("The entered reference is already in use."));
281                         set_focus('ref');
282                         return false;
283                 }
284         }
285
286         if ($_POST['ChargeFreightCost'] == "") {
287                 $_POST['ChargeFreightCost'] = price_format(0);
288         }
289
290         if (!check_num('ChargeFreightCost', 0)) {
291                 display_error(_("The entered shipping value is not numeric."));
292                 set_focus('ChargeFreightCost');
293                 return false;
294         }
295
296         if ($_SESSION['Items']->has_items_dispatch() == 0 && input_num('ChargeFreightCost') == 0) {
297                 display_error(_("There are no item quantities on this invoice."));
298                 return false;
299         }
300
301         if (!check_quantities()) {
302                 display_error(_("Selected quantity cannot be less than quantity credited nor more than quantity not invoiced yet."));
303                 return false;
304         }
305
306         return true;
307 }
308
309 //-----------------------------------------------------------------------------
310 if (isset($_POST['process_invoice']) && check_data()) {
311
312         $newinvoice=  $_SESSION['Items']->trans_no == 0;
313         copy_to_cart();
314         if ($newinvoice) new_doc_date($_SESSION['Items']->document_date);
315
316         $invoice_no = $_SESSION['Items']->write();
317         processing_end();
318
319         if ($newinvoice) {
320                 meta_forward($_SERVER['PHP_SELF'], "AddedID=$invoice_no");
321         } else {
322                 meta_forward($_SERVER['PHP_SELF'], "UpdatedID=$invoice_no");
323         }
324 }
325
326 // find delivery spans for batch invoice display
327 $dspans = array();
328 $lastdn = ''; $spanlen=1;
329
330 for ($line_no = 0; $line_no < count($_SESSION['Items']->line_items); $line_no++) {
331         $line = $_SESSION['Items']->line_items[$line_no];
332         if ($line->quantity == $line->qty_done) {
333                 continue;
334         }
335         if ($line->src_no == $lastdn) {
336                 $spanlen++;
337         } else {
338                 if ($lastdn != '') {
339                         $dspans[] = $spanlen;
340                         $spanlen = 1;
341                 }
342         }
343         $lastdn = $line->src_no;
344 }
345 $dspans[] = $spanlen;
346
347 //-----------------------------------------------------------------------------
348
349 $is_batch_invoice = count($_SESSION['Items']->src_docs) > 1;
350
351 $is_edition = $_SESSION['Items']->trans_type == ST_SALESINVOICE && $_SESSION['Items']->trans_no != 0;
352 start_form();
353 hidden('cart_id');
354
355 start_table(TABLESTYLE2, "width=80%", 5);
356
357 start_row();
358 $colspan = 1;
359 $dim = get_company_pref('use_dimension');
360 if ($dim > 0) 
361         $colspan = 3;
362 label_cells(_("Customer"), $_SESSION['Items']->customer_name, "class='tableheader2'");
363 label_cells(_("Branch"), get_branch_name($_SESSION['Items']->Branch), "class='tableheader2'");
364 if ($_SESSION['Items']->pos['credit_sale'] || $_SESSION['Items']->pos['cash_sale']) {
365  // editable payment type
366         $paymcat = !$_SESSION['Items']->pos['cash_sale'] ? PM_CREDIT :
367                 (!$_SESSION['Items']->pos['credit_sale'] ? PM_CASH : PM_ANY);
368         label_cells(_("Payment terms:"), sale_payment_list('payment', $paymcat),
369                 "class='tableheader2'", "colspan=$colspan");
370 } else
371         label_cells(_('Payment:'), $_SESSION['Items']->payment_terms['terms'], "class='tableheader2'", "colspan=$colspan");
372
373 end_row();
374 start_row();
375
376 if ($_SESSION['Items']->trans_no == 0) {
377         ref_cells(_("Reference"), 'ref', '', null, "class='tableheader2'");
378 } else {
379         label_cells(_("Reference"), $_SESSION['Items']->reference, "class='tableheader2'");
380 }
381
382 //label_cells(_("Delivery Notes:"),
383 //get_customer_trans_view_str(ST_CUSTDELIVERY, array_keys($_SESSION['Items']->src_docs)), "class='tableheader2'");
384
385 label_cells(_("Sales Type"), $_SESSION['Items']->sales_type_name, "class='tableheader2'");
386
387 label_cells(_("Currency"), $_SESSION['Items']->customer_currency, "class='tableheader2'");
388 // 2010-09-03 Joe Hunt
389 if ($dim > 0) 
390         label_cells(_("Dimension"), get_dimension_string($_SESSION['Items']->dimension_id), "class='tableheader2'");
391
392 end_row();
393 start_row();
394
395 if (!isset($_POST['ship_via'])) {
396         $_POST['ship_via'] = $_SESSION['Items']->ship_via;
397 }
398 label_cell(_("Shipping Company"), "class='tableheader2'");
399 shippers_list_cells(null, 'ship_via', $_POST['ship_via']);
400
401 if (!isset($_POST['InvoiceDate']) || !is_date($_POST['InvoiceDate'])) {
402         $_POST['InvoiceDate'] = new_doc_date();
403         if (!is_date_in_fiscalyear($_POST['InvoiceDate'])) {
404                 $_POST['InvoiceDate'] = end_fiscalyear();
405         }
406 }
407
408 date_cells(_("Date"), 'InvoiceDate', '', $_SESSION['Items']->trans_no == 0, 
409         0, 0, 0, "class='tableheader2'", true);
410
411 if (!isset($_POST['due_date']) || !is_date($_POST['due_date'])) {
412         $_POST['due_date'] = get_invoice_duedate($_SESSION['Items']->payment, $_POST['InvoiceDate']);
413 }
414
415 date_cells(_("Due Date"), 'due_date', '', null, 0, 0, 0, "class='tableheader2'");
416 if ($dim > 1) 
417         label_cells(_("Dimension"). " 2", get_dimension_string($_SESSION['Items']->dimension2_id), "class='tableheader2'");
418 else if ($dim > 0)
419         label_cell("&nbsp;", "colspan=2");
420
421 end_row();
422 end_table();
423
424 $row = get_customer_to_order($_SESSION['Items']->customer_id);
425 if ($row['dissallow_invoices'] == 1)
426 {
427         display_error(_("The selected customer account is currently on hold. Please contact the credit control personnel to discuss."));
428         end_form();
429         end_page();
430         exit();
431 }       
432
433 display_heading(_("Invoice Items"));
434
435 div_start('Items');
436 start_table(TABLESTYLE, "width=80%");
437 $th = array(_("Item Code"), _("Item Description"), _("Delivered"), _("Units"), _("Invoiced"),
438         _("This Invoice"), _("Price"), _("Tax Type"), _("Discount"), _("Total"));
439
440 if ($is_batch_invoice) {
441     $th[] = _("DN");
442     $th[] = "";
443 }
444
445 if ($is_edition) {
446     $th[4] = _("Credited");
447 }
448
449 table_header($th);
450 $k = 0;
451 $has_marked = false;
452 $show_qoh = true;
453
454 $dn_line_cnt = 0;
455
456 foreach ($_SESSION['Items']->line_items as $line=>$ln_itm) {
457         if ($ln_itm->quantity == $ln_itm->qty_done) {
458                 continue; // this line was fully invoiced
459         }
460         alt_table_row_color($k);
461         view_stock_status_cell($ln_itm->stock_id);
462
463         text_cells(null, 'Line'.$line.'Desc', $ln_itm->item_description, 30, 50);
464         $dec = get_qty_dec($ln_itm->stock_id);
465         qty_cell($ln_itm->quantity, false, $dec);
466         label_cell($ln_itm->units);
467         qty_cell($ln_itm->qty_done, false, $dec);
468
469         if ($is_batch_invoice) {
470                 // for batch invoices we can only remove whole deliveries
471                 echo '<td nowrap align=right>';
472                 hidden('Line' . $line, $ln_itm->qty_dispatched );
473                 echo number_format2($ln_itm->qty_dispatched, $dec).'</td>';
474         } else {
475                 small_qty_cells(null, 'Line'.$line, qty_format($ln_itm->qty_dispatched, $ln_itm->stock_id, $dec), null, null, $dec);
476         }
477         $display_discount_percent = percent_format($ln_itm->discount_percent*100) . " %";
478
479         $line_total = ($ln_itm->qty_dispatched * $ln_itm->price * (1 - $ln_itm->discount_percent));
480
481         amount_cell($ln_itm->price);
482         label_cell($ln_itm->tax_type_name);
483         label_cell($display_discount_percent, "nowrap align=right");
484         amount_cell($line_total);
485
486         if ($is_batch_invoice) {
487                 if ($dn_line_cnt == 0) {
488                         $dn_line_cnt = $dspans[0];
489                         $dspans = array_slice($dspans, 1);
490                         label_cell($ln_itm->src_no, "rowspan=$dn_line_cnt class=oddrow");
491                         label_cell("<a href='" . $_SERVER['PHP_SELF'] . "?RemoveDN=".
492                                 $ln_itm->src_no."'>" . _("Remove") . "</a>", "rowspan=$dn_line_cnt class=oddrow");
493                 }
494                 $dn_line_cnt--;
495         }
496         end_row();
497 }
498
499 /*Don't re-calculate freight if some of the order has already been delivered -
500 depending on the business logic required this condition may not be required.
501 It seems unfair to charge the customer twice for freight if the order
502 was not fully delivered the first time ?? */
503
504 if (!isset($_POST['ChargeFreightCost']) || $_POST['ChargeFreightCost'] == "") {
505         if ($_SESSION['Items']->any_already_delivered() == 1) {
506                 $_POST['ChargeFreightCost'] = price_format(0);
507         } else {
508                 $_POST['ChargeFreightCost'] = price_format($_SESSION['Items']->freight_cost);
509         }
510
511         if (!check_num('ChargeFreightCost')) {
512                 $_POST['ChargeFreightCost'] = price_format(0);
513         }
514 }
515
516 $accumulate_shipping = get_company_pref('accumulate_shipping');
517 if ($is_batch_invoice && $accumulate_shipping)
518         set_delivery_shipping_sum(array_keys($_SESSION['Items']->src_docs));
519
520 $colspan = 9;
521 start_row();
522 label_cell(_("Shipping Cost"), "colspan=$colspan align=right");
523 small_amount_cells(null, 'ChargeFreightCost', null);
524 if ($is_batch_invoice) {
525 label_cell('', 'colspan=2');
526 }
527
528 end_row();
529 $inv_items_total = $_SESSION['Items']->get_items_total_dispatch();
530
531 $display_sub_total = price_format($inv_items_total + input_num('ChargeFreightCost'));
532
533 label_row(_("Sub-total"), $display_sub_total, "colspan=$colspan align=right","align=right", $is_batch_invoice ? 2 : 0);
534
535 $taxes = $_SESSION['Items']->get_taxes(input_num('ChargeFreightCost'));
536 $tax_total = display_edit_tax_items($taxes, $colspan, $_SESSION['Items']->tax_included, $is_batch_invoice ? 2:0);
537
538 $display_total = price_format(($inv_items_total + input_num('ChargeFreightCost') + $tax_total));
539
540 label_row(_("Invoice Total"), $display_total, "colspan=$colspan align=right","align=right", $is_batch_invoice ? 2 : 0);
541
542 end_table(1);
543 div_end();
544
545 start_table(TABLESTYLE2);
546 textarea_row(_("Memo"), 'Comments', null, 50, 4);
547
548 end_table(1);
549
550 submit_center_first('Update', _("Update"),
551   _('Refresh document page'), true);
552 submit_center_last('process_invoice', _("Process Invoice"),
553   _('Check entered data and save document'), 'default');
554
555 end_form();
556
557 end_page();
558
559 ?>