Huge sales module changes toward delivery and invoicing separation. Includes some...
[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 for Delivery Note(s)"), 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(_("Selected deliveries has been processed"), true);
31
32         display_note(get_customer_trans_view_str($trans_type, $invoice_no, _("View this invoice")), 0, 1);
33
34         display_note(get_gl_view_str($trans_type, $invoice_no, _("View the GL Journal Entries for this Invoice")));
35
36         hyperlink_params("$path_to_root/sales/inquiry/sales_deliveries_view.php", _("Select Another Delivery For Invoicing"), "OutstandingOnly=1");
37
38         display_footer_exit();
39 }
40
41 //---------------------------------------------------------------------------------------------------------------
42
43 if (!isset($_GET['DeliveryNumber']) && !isset($_SESSION['ProcessingDelivery']) && 
44         !isset($_GET['BatchInvoice']) && !isset($_GET['process_invoice'])) 
45 {
46         /* This page can only be called with a delivery for invoicing*/
47         display_error(_("This page can only be opened after delivery selection. Please select delivery to invoicing first."));
48
49         hyperlink_no_params("$path_to_root/sales/inquiry/sales_deliveries_view.php", _("Select Delivery to Invoice"));
50
51         end_page();
52         exit;
53
54 elseif (isset($_GET['DeliveryNumber'])|| isset($_GET['BatchInvoice'])) 
55 {
56
57
58         if (isset($_SESSION['Items']))
59         {
60                 unset($_SESSION['Items']->line_items);
61                 unset ($_SESSION['Items']);
62         }
63
64     if(isset($_GET['BatchInvoice'])) {
65           $_SESSION['ProcessingDelivery'] = $_SESSION['DeliveryBatch'];
66           unset($_SESSION['DeliveryBatch']);
67     }
68     else
69           $_SESSION['ProcessingDelivery'] = array($_GET['DeliveryNumber']);
70
71     $_SESSION['Items'] = new cart('delivery');
72     $_SESSION['Items']->trans_no = $_SESSION['ProcessingDelivery'];
73
74         /*read in all the selected deliveries into the Items cart  */
75         if (read_sales_delivery($_SESSION['ProcessingDelivery'], $_SESSION['Items'])) 
76         {
77           if ($_SESSION['Items']->count_items() == 0) 
78           {
79                 hyperlink_params($path_to_root . "/sales/inquiry/sales_deliveries_view.php", _("Select a different delivery to invoice"), "OutstandingOnly=1");
80                 die ("<br><b>" . _("There are no delivered items with a quantity left to invoice. There is nothing left to invoice.") . "</b>");
81       }
82         } 
83         else 
84         {
85                 hyperlink_no_params("/sales_deliveries_view.php", _("Select a delivery note to invoice"));
86                 die ("<br><b>" . _("This delivery note could not be retrieved. Please select another delivery.") . "</b>");
87         }
88
89 else 
90 {
91         foreach ($_SESSION['Items']->line_items as $line=>$itm) 
92         {
93           if(isset($_POST['Line'.$line])) {
94                 $line_qty = $_POST['Line'.$line];
95                 if (is_numeric($line_qty) && $_POST['Line'.$line] <= ($itm->quantity - $itm->qty_done))
96                 {
97                         $_SESSION['Items']->line_items[$line]->qty_dispatched = $line_qty;
98                 }
99           }
100         
101           if(isset($_POST['Line'.$line.'Desc'])) {
102                 $line_desc = $_POST['Line'.$line.'Desc'];
103                 if (strlen($line_desc) > 0) 
104                 {
105                         $_SESSION['Items']->line_items[$line]->item_description = $line_desc;
106                 }
107           }
108         }
109 }
110
111 //---------------------------------------------------------------------------------------------------------------
112
113 function delivery_changed_error()
114 {
115         global $path_to_root;
116         display_note(_("This delivery note has been changed or invoiced since this invoice was started to be confirmed. Processing halted."), 1, 0);
117         display_note(_("To enter and confirm this 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_deliveries_view.php", _("Select a sales order for invoicing"));
120
121         unset($_SESSION['ProcessingDelivery']);
122         exit;
123 }
124
125 //---------------------------------------------------------------------------------------------------------------
126
127 function check_delivery_changed()
128 {
129         global $debug;
130
131         /*Now need to check that the delivery note details are the same 
132         as they were when they were read into the Items array.
133         If they've changed then someone else may have invoiced them */
134
135         $sql = "SELECT id, stock_id, quantity, qty_done FROM "
136                 .TB_PREF."debtor_trans_details WHERE
137                 debtor_trans_type = 13 AND (";
138
139         foreach($_SESSION['Items']->trans_no as $key=>$num) {
140             if($key!=0) $sql .= ' OR ';
141             $sql .= 'debtor_trans_no =' . $num;
142         }
143         $sql .= ') ORDER BY id';
144         $result = db_query($sql,"while checking delivery changes");
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         $line=0;
161         while ($myrow = db_fetch($result)) 
162         {
163                 $stk_itm = $myrow["stock_id"];
164
165                 if ($_SESSION['Items']->line_items[$line]->quantity != -$myrow["quantity"] ||
166                         $_SESSION['Items']->line_items[$line]->qty_done != -$myrow["qty_done"])
167                 {
168                         display_note(_("Original delivery for line #") . $line+1 . ' '.
169                                 _("has a quantity of") . " " . -$myrow["quantity"] . " " . 
170                                 _("and an delivered quantity of") . " " . -$myrow["qty_done"] . "." .
171                                 _("Now the quantity of") . " " . 
172                                 $_SESSION['Items']->line_items[$line]->quantity . " " . 
173                                 _("and invoiced quantity of") . " " . 
174                                 $_SESSION['Items']->line_items[$line]->qty_done, 1, 0);
175
176                         return false;
177                 }
178         $line++;
179         } /*loop through all line items of the order to ensure none have been invoiced */
180         return true;
181 }
182
183
184 //---------------------------------------------------------------------------------------------------------------
185
186 function check_data()
187 {
188         if (!isset($_POST['InvoiceDate']) || !is_date($_POST['InvoiceDate']))   
189         {
190                 display_error(_("The entered invoice date is invalid."));
191                 return false;
192         }
193         if (!is_date_in_fiscalyear($_POST['InvoiceDate'])) 
194         {
195                 display_error(_("The entered invoice date is not in fiscal year."));
196                 return false;
197         }
198         if (!isset($_POST['due_date']) || !is_date($_POST['due_date'])) 
199         {
200                 display_error(_("The entered invoice due date is invalid."));
201                 return false;
202         }
203
204         if (!references::is_valid($_POST['ref'])) 
205         {
206                 display_error(_("You must enter a reference."));
207                 return false;
208         }
209
210         if (!is_new_reference($_POST['ref'], 10)) 
211         {
212                 display_error(_("The entered reference is already in use."));
213                 return false;
214         }
215         if ($_POST['ChargeFreightCost'] == "")
216                 $_POST['ChargeFreightCost'] = 0;
217         if (!is_numeric($_POST['ChargeFreightCost']) || $_POST['ChargeFreightCost'] < 0)        
218         {
219                 display_error(_("The entered shipping value is not numeric."));
220                 return false;
221         }
222
223         if ($_SESSION['Items']->has_items_dispatch() == 0 && $_POST['ChargeFreightCost'] == 0)  
224         {
225                 display_error(_("There are no item quantities on this invoice."));
226                 return false;
227         }
228
229         return true;
230 }
231
232 //---------------------------------------------------------------------------------------------------------------
233
234 function process_invoice($invoicing=false)
235 {
236         if ($invoicing) 
237         {
238                 read_sales_delivery($_SESSION['Items']->trans_no, $_SESSION['Items']);
239                 $duedate = get_invoice_duedate($_SESSION['Items']->customer_id, $_SESSION['Items']->delivery_date);
240                 $invoice_no = add_sales_invoice($_SESSION['Items'],
241                         $_SESSION['Items']->delivery_date, $duedate,
242                         $_SESSION['Items']->tax_group_id, $_SESSION['Items']->freight_cost,
243                         $_SESSION['Items']->Location, $_SESSION['Items']->ship_via,
244                         $_SESSION['Items']->default_sales_type, references::get_next(10),
245                         $_SESSION['Items']->memo_);
246         } 
247         else 
248         {
249         
250                 if (!check_data())
251                         return;
252
253                 if (!check_delivery_changed())
254                         delivery_changed_error();
255
256                 $invoice_no = add_sales_invoice($_SESSION['Items'],
257                         $_POST['InvoiceDate'], $_POST['due_date'],
258                         $_SESSION['Items']->tax_group_id,       
259                         $_POST['ChargeFreightCost'], 
260                         $_SESSION['Items']->Location, 
261                         $_POST['ship_via'],     $_POST['sales_type_id'], $_POST['ref'],
262                         $_POST['InvoiceText']); 
263                 unset($_SESSION['ProcessingDelivery']);
264         }
265         meta_forward($_SERVER['PHP_SELF'], "AddedID=$invoice_no");
266 }
267
268 //---------------------------------------------------------------------------------------------------------------
269 if (isset($_GET['process_invoice']))
270         process_invoice(true);
271 elseif (isset($_POST['process_invoice']))
272         process_invoice();
273
274 //-------------------------------------------------------------------------------------------------
275
276 start_form(false, true);
277
278 start_table("$table_style2 width=80%", 5);
279 //echo "<tr><td>"; // outer table
280
281 //start_table("$table_style width=100%");
282 start_row();
283 label_cells(_("Customer"), $_SESSION['Items']->customer_name, "class='tableheader2'");
284 label_cells(_("Branch"), get_branch_name($_SESSION['Items']->Branch), "class='tableheader2'");
285 label_cells(_("Currency"), $_SESSION['Items']->customer_currency, "class='tableheader2'");
286 end_row();
287 start_row();
288
289 if (!isset($_POST['ref']))
290         $_POST['ref'] = references::get_next(10);
291
292 ref_cells(_("Reference"), 'ref', null, "class='tableheader2'");
293
294 label_cells(_("Delivery Notes:"), get_customer_trans_view_str(systypes::cust_dispatch(), $_SESSION['ProcessingDelivery']), "class='tableheader2'");
295
296
297 if (!isset($_POST['sales_type_id']))
298         $_POST['sales_type_id'] = $_SESSION['Items']->default_sales_type;
299 label_cell(_("Sales Type"), "class='tableheader2'");    
300 sales_types_list_cells(null, 'sales_type_id', $_POST['sales_type_id']);
301
302 end_row();
303 start_row();
304
305 if (!isset($_POST['ship_via']))
306         $_POST['ship_via'] = $_SESSION['Items']->ship_via;
307 label_cell(_("Shipping Company"), "class='tableheader2'");      
308 shippers_list_cells(null, 'ship_via', $_POST['ship_via']);
309
310 if (!isset($_POST['InvoiceDate']) || !is_date($_POST['InvoiceDate']))
311 {
312         $_POST['InvoiceDate'] = Today();
313         if (!is_date_in_fiscalyear($_POST['InvoiceDate']))
314                 $_POST['InvoiceDate'] = end_fiscalyear();
315 }
316
317 date_cells(_("Date"), 'InvoiceDate', $_POST['InvoiceDate'], 0, 0, 0, "class='tableheader'");
318 //end_table();
319
320 //echo "</td><td>";// outer table
321
322 //start_table("$table_style width=90%");
323
324 if (!isset($_POST['due_date']) || !is_date($_POST['due_date']))
325
326         $_POST['due_date'] = get_invoice_duedate($_SESSION['Items']->customer_id, $_POST['InvoiceDate']);
327
328 date_cells(_("Due Date"), 'due_date', $_POST['due_date'], 0, 0, 0, "class='tableheader'");
329 //end_table();
330
331 //echo "</td></tr>";
332 end_row();
333 end_table(); // outer table
334
335 display_heading(_("Invoice Items"));
336
337 start_table("$table_style width=80%");
338 $th = array(_("Item Code"), _("Item Description"), _("Delivered"), _("Units"), _("Invoiced"),
339         _("This Invoice"), _("Price"), _("Tax Type"), _("Discount"), _("Total"));
340 table_header($th);
341 $k = 0;
342 $has_marked = false;
343 $show_qoh = true;
344
345 foreach ($_SESSION['Items']->line_items as $line=>$ln_itm) 
346 {
347         if($ln_itm->quantity==$ln_itm->qty_done) continue; //this line is fully delivered
348         alt_table_row_color($k);
349
350         view_stock_status_cell($ln_itm->stock_id); // ?
351
352         text_cells(null, 'Line'.$line.'Desc', $ln_itm->item_description, 30, 50);
353         qty_cell($ln_itm->quantity);
354         label_cell($ln_itm->units);
355         qty_cell($ln_itm->qty_done);
356
357         if(count($_SESSION['Items']->trans_no)>1) {
358             // for batch invoices we can process only whole deliveries
359             qty_cell($ln_itm->qty_dispatched);
360             hidden('Line'.$line,$ln_itm->qty_dispatched );
361         } else
362             text_cells(null, 'Line'.$line, $ln_itm->qty_dispatched, 10, 10);
363
364         $display_discount_percent = number_format2($ln_itm->discount_percent*100,user_percent_dec()) . "%";
365
366         $line_total = ($ln_itm->qty_dispatched * $ln_itm->price * (1 - $ln_itm->discount_percent));
367
368         amount_cell($ln_itm->price);
369         label_cell($ln_itm->tax_type_name);
370         label_cell($display_discount_percent, "nowrap align=right");
371         amount_cell($line_total);
372
373         end_row();
374 }
375
376 /*Don't re-calculate freight if some of the order has already been delivered -
377 depending on the business logic required this condition may not be required.
378 It seems unfair to charge the customer twice for freight if the order
379 was not fully delivered the first time ?? */
380
381 if (!isset($_POST['ChargeFreightCost']) || $_POST['ChargeFreightCost'] == "") 
382 {
383     if ($_SESSION['Items']->any_already_delivered() == 1) 
384     {
385         $_POST['ChargeFreightCost'] = 0;
386     } 
387     else 
388     {
389         $_POST['ChargeFreightCost'] = $_SESSION['Items']->freight_cost;
390     }
391     if (!is_numeric($_POST['ChargeFreightCost']))
392     {
393         $_POST['ChargeFreightCost'] = 0;
394     }
395 }
396
397 start_row();
398
399 small_amount_cells(_("Shipping Cost"), 'ChargeFreightCost', null, "colspan=9 align=right");
400
401 $inv_items_total = $_SESSION['Items']->get_items_total_dispatch();
402
403 $display_sub_total = number_format2($inv_items_total + $_POST['ChargeFreightCost'],user_price_dec());
404
405 label_row(_("Sub-total"), $display_sub_total, "colspan=9 align=right","align=right");
406
407 $taxes = $_SESSION['Items']->get_taxes($_SESSION['Items']->tax_group_id, $_POST['ChargeFreightCost']);
408 $tax_total = display_edit_tax_items($taxes, 9);
409
410 $display_total = number_format2(($inv_items_total + $_POST['ChargeFreightCost'] + $tax_total), user_price_dec());
411
412 label_row(_("Invoice Total"), $display_total, "colspan=9 align=right","align=right");
413
414 end_table(1);
415
416 //if ($has_marked)
417 //      display_note(_("Marked items have insufficient quantities in stock."), 0, 1, "class='red'");
418
419 start_table($table_style2);
420
421 textarea_row(_("Memo"), 'InvoiceText', null, 50, 4);
422
423 end_table(1);
424
425 submit_center_first('Update', _("Update"));
426 submit_center_last('process_invoice', _("Process Invoice"));
427
428 end_form();
429
430 //---------------------------------------------------------------------------------------------
431
432 end_page();
433
434 ?>