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