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