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