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