Missing Date Picker
[fa-stable.git] / sales / customer_invoice.php
1 <?php
2
3 $page_security = 2;
4 $path_to_root="..";
5 include_once($path_to_root . "/sales/includes/cart_class.inc");
6 include_once($path_to_root . "/includes/session.inc");
7
8 include_once($path_to_root . "/includes/data_checks.inc");
9
10 include_once($path_to_root . "/includes/manufacturing.inc");
11 include_once($path_to_root . "/sales/includes/sales_db.inc");
12 include_once($path_to_root . "/sales/includes/sales_ui.inc");
13
14 include_once($path_to_root . "/taxes/tax_calc.inc");
15
16 $js = "";
17 if ($use_popup_windows)
18         $js .= get_js_open_window(900, 500);
19 if ($use_date_picker)
20         $js .= get_js_date_picker();
21 page(_("Issue an Invoice and Deliver Items for a Sales Order"), false, false, "", $js);
22
23 //---------------------------------------------------------------------------------------------------------------
24
25 if (isset($_GET['AddedID'])) 
26 {
27         $invoice_no = $_GET['AddedID'];
28         $trans_type = 10;
29
30         display_notification(_("Invoice processed"), true);
31         display_note(get_customer_trans_view_str($trans_type, $invoice_no, _("View this invoice")), 0, 1);
32
33         display_note(get_gl_view_str($trans_type, $invoice_no, _("View the GL Journal Entries for this Invoice")));
34
35         if ($_SESSION['Items']->direct_invoice)
36                 hyperlink_params("$path_to_root/sales/sales_order_entry.php", _("Issue Another Invoice"), "NewInvoice=Yes");
37         else
38                 hyperlink_params("$path_to_root/sales/inquiry/sales_orders_view.php", _("Select Another Order For Invoicing"), "OutstandingOnly=1");
39
40         unset($_SESSION['Items']->line_items);
41         unset($_SESSION['Items']);
42         display_footer_exit();
43 }
44
45 //---------------------------------------------------------------------------------------------------------------
46
47 if (!isset($_GET['OrderNumber']) && !isset($_SESSION['ProcessingOrder']) && 
48         !isset($_GET['process_invoice'])) 
49 {
50         /* This page can only be called with an order number for invoicing*/
51         display_error(_("This page can only be opened if an order has been selected. Please select an order first."));
52
53         hyperlink_no_params("$path_to_root/sales/inquiry/sales_orders_view.php", _("Select a sales order to invoice"));
54
55         end_page();
56         exit;
57
58
59 elseif (isset($_GET['OrderNumber']) && $_GET['OrderNumber'] > 0) 
60 {
61
62         if (isset($_SESSION['Items']))
63         {
64                 unset($_SESSION['Items']->line_items);
65                 unset ($_SESSION['Items']);
66         }
67
68         session_register("Items");
69         session_register("ProcessingOrder");
70
71         $_SESSION['ProcessingOrder'] = $_GET['OrderNumber'];
72         $_SESSION['Items'] = new cart;
73
74         /*read in all the selected order into the Items cart  */
75
76         if (read_sales_order($_SESSION['ProcessingOrder'], $_SESSION['Items'], true)) 
77         {
78
79         if ($_SESSION['Items']->count_items() == 0) 
80         {
81                 hyperlink_params($path_to_root . "/sales/inquiry/sales_orders_view.php", _("Select a different sales order to invoice"), "OutstandingOnly=1");
82                 die ("<br><b>" . _("There are no ordered items with a quantity left to deliver. There is nothing left to invoice.") . "</b>");
83         }
84         } 
85         else 
86         {
87                 hyperlink_no_params("/sales_orders_view.php", _("Select a sales order to invoice"));
88                 die ("<br><b>" . _("This order item could not be retrieved. Please select another order.") . "</b>");
89         }
90
91
92 else 
93 {
94         /* if processing, a dispatch page has been called and ${$StkItm->stock_id} would have been set from the post */
95         foreach ($_SESSION['Items']->line_items as $itm) 
96         {
97
98                 if (isset($_SESSION['Items']->line_items[$itm->stock_id]) && 
99                         isset($_POST[$itm->stock_id]) && is_numeric($_POST[$itm->stock_id]) && 
100                         $_POST[$itm->stock_id] <= ($_SESSION['Items']->line_items[$itm->stock_id]->quantity - 
101                         $_SESSION['Items']->line_items[$itm->stock_id]->qty_inv))
102                 {
103                         $_SESSION['Items']->line_items[$itm->stock_id]->qty_dispatched = $_POST[$itm->stock_id];
104                 }
105
106                 if (isset($_POST[$itm->stock_id . "Desc"]) && strlen($_POST[$itm->stock_id . "Desc"]) > 0) 
107                 {
108                         $_SESSION['Items']->line_items[$itm->stock_id]->item_description = $_POST[$itm->stock_id . "Desc"];
109                 }
110         }
111 }
112
113 //---------------------------------------------------------------------------------------------------------------
114
115 function order_changed_error()
116 {
117         global $path_to_root;
118         display_note(_("This order has been changed or invoiced since this delivery was started to be confirmed. Processing halted."), 1, 0);
119         display_note(_("To enter and confirm this dispatch/invoice the order must be re-selected and re-read again to update the changes made by the other user."), 1, 0);
120
121         hyperlink_no_params("$path_to_root/sales/inquiry/sales_orders_view.php", _("Select a sales order for confirming deliveries and invoicing"));
122
123         unset($_SESSION['Items']->line_items);
124         unset($_SESSION['Items']);
125         unset($_SESSION['ProcessingOrder']);
126         exit;
127 }
128
129 //---------------------------------------------------------------------------------------------------------------
130
131 function check_order_changed()
132 {
133         global $debug;
134
135         /*Now need to check that the order details are the same as they were when
136                         they were read into the Items array.
137         If they've changed then someone else may have invoiced them  -
138                 as modified for bug pointed out by Sherif 1-7-03*/
139
140         $sql = "SELECT stk_code, quantity, qty_invoiced FROM ".TB_PREF."sales_order_details WHERE
141                 quantity - qty_invoiced > 0
142                 AND order_no = " . $_SESSION['ProcessingOrder'];
143
144         $result = db_query($sql,"retreive sales order details");
145
146         if (db_num_rows($result) != count($_SESSION['Items']->line_items))
147         {
148
149                 /*there should be the same number of items returned from this query as there are lines on the invoice -
150                         if  not then someone has already invoiced or credited some lines */
151         if ($debug == 1)
152         {
153                 display_note($sql, 1, 0);
154                 display_note("No rows returned by sql:" . db_num_rows($result), 1, 0);
155                 display_note("Count of items in the session " . count($_SESSION['Items']->line_items), 1, 0);
156         }
157
158                 return false;
159         }
160
161         while ($myrow = db_fetch($result)) 
162         {
163                 $stk_itm = $myrow["stk_code"];
164                 if ($_SESSION['Items']->line_items[$stk_itm]->quantity != $myrow["quantity"] ||
165                         $_SESSION['Items']->line_items[$stk_itm]->qty_inv != $myrow["qty_invoiced"])
166                 {
167                         display_note(_("Original order for") . " " . $myrow["stk_code"] . " " .
168                                 _("has a quantity of") . " " . $myrow["quantity"] . " " . 
169                                 _("and an invoiced quantity of") . " " . $myrow["qty_invoiced"] . " " .
170                                 _("the session shows quantity of") . " " . 
171                                 $_SESSION['Items']->line_items[$stk_itm]->quantity . " " . 
172                                 _("and quantity invoice of") . " " . 
173                                 $_SESSION['Items']->line_items[$stk_itm]->qty_inv, 1, 0);
174
175                         return false;
176                 }
177         } /*loop through all line items of the order to ensure none have been invoiced */
178
179         return true;
180 }
181
182
183 //---------------------------------------------------------------------------------------------------------------
184
185 function check_data()
186 {
187         if (!isset($_POST['DispatchDate']) || !is_date($_POST['DispatchDate'])) 
188         {
189                 display_error(_("The entered invoice date is invalid."));
190                 return false;
191         }
192         if (!is_date_in_fiscalyear($_POST['DispatchDate'])) 
193         {
194                 display_error(_("The entered invoice date is not in fiscal year."));
195                 return false;
196         }
197         if (!isset($_POST['due_date']) || !is_date($_POST['due_date'])) 
198         {
199                 display_error(_("The entered invoice due date is invalid."));
200                 return false;
201         }
202
203         if (!references::is_valid($_POST['ref'])) 
204         {
205                 display_error(_("You must enter a reference."));
206                 return false;
207         }
208
209         if (!is_new_reference($_POST['ref'], 10)) 
210         {
211                 display_error(_("The entered reference is already in use."));
212                 return false;
213         }
214         if ($_POST['ChargeFreightCost'] == "")
215                 $_POST['ChargeFreightCost'] = 0;
216         if (!is_numeric($_POST['ChargeFreightCost']) || $_POST['ChargeFreightCost'] < 0)        
217         {
218                 display_error(_("The entered shipping value is not numeric."));
219                 return false;
220         }
221
222         if ($_SESSION['Items']->has_items_dispatch() == 0 && $_POST['ChargeFreightCost'] == 0)  
223         {
224                 display_error(_("There are no item quantities on this invoice."));
225                 return false;
226         }
227
228         return true;
229 }
230
231 //---------------------------------------------------------------------------------------------------------------
232
233 function check_qoh()
234 {
235         if (!sys_prefs::allow_negative_stock())
236         {
237         foreach ($_SESSION['Items']->line_items as $itm) 
238         {
239
240                         if ($itm->qty_dispatched && has_stock_holding($itm->mb_flag))
241                         {
242                                 $qoh = get_qoh_on_date($itm->stock_id, $_POST['Location'], $_POST['DispatchDate']);
243
244                         if ($itm->qty_dispatched > $qoh) 
245                         {
246                                 display_error(_("The invoice cannot be processed because there is an insufficient quantity for component:") .
247                                         " " . $itm->stock_id . " - " .  $itm->item_description);
248                                 return false;
249                         }
250                 }
251         }
252         }
253
254         return true;
255 }
256
257 //---------------------------------------------------------------------------------------------------------------
258
259 function process_invoice($invoicing=false)
260 {
261         if ($invoicing) 
262         {
263                 read_sales_order($_SESSION['Items']->order_no, $_SESSION['Items'], true);
264                 $duedate = get_invoice_duedate($_SESSION['Items']->customer_id, $_SESSION['Items']->delivery_date);
265                 $invoice_no = add_sales_invoice($_SESSION['Items'],
266                         $_SESSION['Items']->delivery_date, $duedate, $_SESSION['Items']->order_no,
267                         $_SESSION['Items']->tax_group_id, $_SESSION['Items']->freight_cost,
268                         $_SESSION['Items']->Location, $_SESSION['Items']->ship_via,
269                         $_SESSION['Items']->default_sales_type, references::get_next(10),
270                         $_SESSION['Items']->memo_, 0);
271         } 
272         else 
273         {
274         
275                 if (!check_data())
276                         return;
277
278                 if (!check_order_changed())
279                         order_changed_error();
280
281                 if (!check_qoh())
282                         return;
283
284                 if ($_POST['bo_policy'])
285                         $bo_policy = 0;
286                 else
287                         $bo_policy = 1;
288
289                 $invoice_no = add_sales_invoice($_SESSION['Items'],
290                         $_POST['DispatchDate'], $_POST['due_date'],     $_SESSION['ProcessingOrder'],
291                         $_POST['tax_group_id'], $_POST['ChargeFreightCost'], $_POST['Location'],
292                         $_POST['ship_via'],     $_POST['sales_type_id'], $_POST['ref'],
293                         $_POST['InvoiceText'], $bo_policy);
294                 unset($_SESSION['ProcessingOrder']);
295         }
296
297         meta_forward($_SERVER['PHP_SELF'], "AddedID=$invoice_no");
298 }
299
300 //---------------------------------------------------------------------------------------------------------------
301 if (isset($_GET['process_invoice']))
302         process_invoice(true);
303 elseif (isset($_POST['process_invoice']))
304         process_invoice();
305
306 //-------------------------------------------------------------------------------------------------
307
308 start_form(false, true);
309
310 start_table("$table_style2 width=80%", 5);
311 echo "<tr><td>"; // outer table
312
313 start_table("$table_style width=100%");
314 start_row();
315 label_cells(_("Customer"), $_SESSION['Items']->customer_name, "class='tableheader2'");
316 label_cells(_("Branch"), get_branch_name($_SESSION['Items']->Branch), "class='tableheader2'");
317 label_cells(_("Currency"), $_SESSION['Items']->customer_currency, "class='tableheader2'");
318 end_row();
319 start_row();
320
321 if (!isset($_POST['ref']))
322         $_POST['ref'] = references::get_next(10);
323
324 ref_cells(_("Reference"), 'ref', null, "class='tableheader2'");
325
326 if (!isset($_POST['tax_group_id']))
327         $_POST['tax_group_id'] = $_SESSION['Items']->tax_group_id;
328 label_cell(_("Tax Group"), "class='tableheader2'");     
329 tax_groups_list_cells(null, 'tax_group_id', $_POST['tax_group_id'], false, null, true);
330
331 label_cells(_("For Sales Order"), get_customer_trans_view_str(systypes::sales_order(), $_SESSION['ProcessingOrder']), "class='tableheader2'");
332
333 end_row();
334 start_row();
335
336 if (!isset($_POST['sales_type_id']))
337         $_POST['sales_type_id'] = $_SESSION['Items']->default_sales_type;
338 label_cell(_("Sales Type"), "class='tableheader2'");    
339 sales_types_list_cells(null, 'sales_type_id', $_POST['sales_type_id']);
340
341 if (!isset($_POST['Location']))
342         $_POST['Location'] = $_SESSION['Items']->Location;
343 label_cell(_("Delivery From"), "class='tableheader2'"); 
344 locations_list_cells(null, 'Location', $_POST['Location'], false, true);
345
346 if (!isset($_POST['ship_via']))
347         $_POST['ship_via'] = $_SESSION['Items']->ship_via;
348 label_cell(_("Shipping Company"), "class='tableheader2'");      
349 shippers_list_cells(null, 'ship_via', $_POST['ship_via']);
350 end_row();
351
352 end_table();
353
354 echo "</td><td>";// outer table
355
356 start_table("$table_style width=90%");
357
358 // set this up here cuz it's used to calc qoh
359 if (!isset($_POST['DispatchDate']) || !is_date($_POST['DispatchDate']))
360 {
361         $_POST['DispatchDate'] = Today();
362         if (!is_date_in_fiscalyear($_POST['DispatchDate']))
363                 $_POST['DispatchDate'] = end_fiscalyear();
364 }
365 date_row(_("Date"), 'DispatchDate', $_POST['DispatchDate'], 0, 0, 0, "class='tableheader'");
366
367 if (!isset($_POST['due_date']) || !is_date($_POST['due_date']))
368         //$_POST['due_date'] = $_POST['DispatchDate'];
369         $_POST['due_date'] = get_invoice_duedate($_SESSION['Items']->customer_id, $_POST['DispatchDate']);
370
371 date_row(_("Due Date"), 'due_date', $_POST['due_date'], 0, 0, 0, "class='tableheader'");
372 end_table();
373
374 echo "</td></tr>";
375 end_table(1); // outer table
376
377 display_heading(_("Invoice Items"));
378
379 start_table("$table_style width=80%");
380 $th = array(_("Item Code"), _("Item Description"), _("Ordered"), _("Units"), _("Delivered"),
381         _("This Delivery"), _("Price"), _("Tax Type"), _("Discount"), _("Total"));
382 table_header($th);
383 $k = 0;
384 $has_marked = false;
385 $show_qoh = true;
386
387 foreach ($_SESSION['Items']->line_items as $ln_itm) 
388 {
389
390     // if it's a non-stock item (eg. service) don't show qoh
391     if (sys_prefs::allow_negative_stock() || !has_stock_holding($ln_itm->mb_flag) ||
392                 $ln_itm->qty_dispatched == 0)
393         $show_qoh = false;
394
395         if ($show_qoh)
396                 $qoh = get_qoh_on_date($ln_itm->stock_id, $_POST['Location'], $_POST['DispatchDate']);
397
398         if ($show_qoh && ($ln_itm->qty_dispatched > $qoh)) 
399         {
400                 // oops, we don't have enough of one of the component items
401                 start_row("class='stockmankobg'");
402                 $has_marked = true;
403         } 
404         else
405                 alt_table_row_color($k);
406
407         view_stock_status_cell($ln_itm->stock_id);
408
409         text_cells(null, $ln_itm->stock_id . "Desc", $ln_itm->item_description, 30, 50);
410         qty_cell($ln_itm->quantity);
411         label_cell($ln_itm->units);
412         qty_cell($ln_itm->qty_inv);
413
414         text_cells(null, $ln_itm->stock_id, $ln_itm->qty_dispatched, 10, 10);
415
416         $display_discount_percent = number_format2($ln_itm->discount_percent*100,user_percent_dec()) . "%";
417
418         $line_total = ($ln_itm->qty_dispatched * $ln_itm->price * (1 - $ln_itm->discount_percent));
419
420         amount_cell($ln_itm->price);
421         label_cell($ln_itm->tax_type_name);
422         label_cell($display_discount_percent, "nowrap align=right");
423         amount_cell($line_total);
424
425         //label_cell(get_tax_free_price_for_item($ln_itm->stock_id, $line_total, $_POST['tax_group_id']));
426
427         end_row();
428 }
429
430 /*Don't re-calculate freight if some of the order has already been delivered -
431 depending on the business logic required this condition may not be required.
432 It seems unfair to charge the customer twice for freight if the order
433 was not fully delivered the first time ?? */
434
435 if (!isset($_POST['ChargeFreightCost']) || $_POST['ChargeFreightCost'] == "") 
436 {
437     if ($_SESSION['Items']->any_already_delivered() == 1) 
438     {
439         $_POST['ChargeFreightCost'] = 0;
440     } 
441     else 
442     {
443         $_POST['ChargeFreightCost'] = $_SESSION['Items']->freight_cost;
444     }
445     if (!is_numeric($_POST['ChargeFreightCost']))
446     {
447         $_POST['ChargeFreightCost'] = 0;
448     }
449 }
450
451 start_row();
452
453 small_amount_cells(_("Shipping Cost"), 'ChargeFreightCost', null, "colspan=9 align=right");
454
455 $inv_items_total = $_SESSION['Items']->get_items_total_dispatch();
456
457 $display_sub_total = number_format2($inv_items_total + $_POST['ChargeFreightCost'],user_price_dec());
458
459 label_row(_("Sub-total"), $display_sub_total, "colspan=9 align=right","align=right");
460
461 $taxes = $_SESSION['Items']->get_taxes($_POST['tax_group_id'], $_POST['ChargeFreightCost']);
462 $tax_total = display_edit_tax_items($taxes, 9);
463
464 $display_total = number_format2(($inv_items_total + $_POST['ChargeFreightCost'] + $tax_total), user_price_dec());
465
466 label_row(_("Invoice Total"), $display_total, "colspan=9 align=right","align=right");
467
468 end_table(1);
469
470 if ($has_marked)
471         display_note(_("Marked items have insufficient quantities in stock."), 0, 1, "class='red'");
472
473 start_table($table_style2);
474
475 policy_list_row(_("Action For Balance"), "bo_policy", null);
476
477 textarea_row(_("Memo"), 'InvoiceText', null, 50, 4);
478
479 end_table(1);
480
481 submit_center_first('Update', _("Update"));
482 submit_center_last('process_invoice', _("Process Invoice"));
483
484 end_form();
485
486 //---------------------------------------------------------------------------------------------
487
488 end_page();
489
490 ?>