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/* Definition of the cart class 13this class can hold all the information for: 14 15i) a sales order 16ii) an invoice 17iii) a credit note 18iv) a delivery note 19*/ 20 21include_once($path_to_root . "/inventory/includes/inventory_db.inc"); 22include_once($path_to_root . "/taxes/tax_calc.inc"); 23 24class cart 25{ 26 var $trans_type; // invoice, order, quotation, delivery note ... 27 var $trans_no = array();// array (num1=>ver1,..) or 0 for new 28 var $so_type = 0; // for sales order: simple=0 template=1 29 var $cart_id; // used to detect multi-tab edition conflits 30 var $line_items; //array of objects of class line_details 31 32 var $src_docs = array(); // array of arrays(num1=>ver1,...) or 0 for no src 33 var $src_date; // src document date (for info only) 34 35 var $document_date; 36 var $due_date; 37 var $sales_type; // set to the customer's sales type 38 var $sales_type_name; // set to customer's sales type name 39 var $tax_included; 40 41 var $customer_currency; // set to the customer's currency 42 var $default_discount; // set to the customer's discount % 43 var $customer_name; 44 var $customer_id; 45 var $Branch; 46 var $email; 47 48 var $deliver_to; 49 var $delivery_address; 50 var $phone; 51 52 var $cust_ref; 53 var $reference; 54 var $Comments; 55 var $Location; 56 var $location_name; 57 var $order_no; // the original order number 58 59 var $ship_via; 60 var $freight_cost = 0; 61 62 var $tax_group_id; 63 var $tax_group_name; 64 var $tax_group_array = null; // saves db queries 65 var $price_factor; // ditto for price calculations 66 67 var $pos; // user assigned POS (contains cash accont number/name) 68 var $cash_discount; // not used as of FA 2.1 69 var $dimension_id; 70 var $dimension2_id; 71 var $payment; 72 var $payment_terms; // cached payment terms 73 var $credit; 74 var $ex_rate; 75 76 //------------------------------------------------------------------------- 77 // 78 // $trans_no==0 => open new/direct document 79 // $trans_no!=0 && $prep_child==false => update with parent constarints for reedition 80 // $trans_no!=0 && $prep_child==true => prepare for child doc entry 81 // 82 function Cart($type, $trans_no=0, $prep_child=false) { 83 /*Constructor function initialises a new shopping cart */ 84 $this->line_items = array(); 85 $this->sales_type = ""; 86 if ($type == ST_SALESQUOTE) 87 $this->trans_type = $type; 88 else 89 $this->trans_type = ST_SALESORDER; 90 $this->dimension_id = 0; 91 $this->dimension2_id = 0; 92 $this->pos = get_sales_point(user_pos()); 93 $this->read($type, $trans_no, $prep_child); 94 $this->cart_id = uniqid(''); 95 } 96 97 /* 98 Optional sorting items by stock_id. 99 */ 100 function _cmp_lines($a, $b) 101 { 102 return strcmp($a->stock_id, $b->stock_id); 103 } 104 105 /* 106 Returns items array optionally sorted by item code. 107 */ 108 function get_items() 109 { 110 global $sort_sales_items; 111 112 $items = $this->line_items; 113 if (@$sort_sales_items) 114 uasort($items, array($this, '_cmp_lines')); 115 116 return $items; 117 } 118 // 119 // Prepare cart to new child document entry, just after initial parent doc read. 120 // 121 function prepare_child() 122 { 123 global $Refs; 124 125 $type = get_child_type($this->trans_type); 126 127 $this->trans_type = $type; 128 $this->reference = $Refs->get_next($this->trans_type); 129 if ($type == ST_CUSTCREDIT) 130 $this->src_date = $this->document_date; 131 132 $this->document_date = new_doc_date(); 133 134 for($line_no = 0; $line_no < count($this->line_items); $line_no++) { 135 $line = &$this->line_items[$line_no]; 136 $line->src_id = $line->id; // save src line ids for update 137 $line->qty_dispatched = $type == ST_CUSTCREDIT ? '0' : $line->quantity - $line->qty_done; 138 $line->qty_old = 0; 139 } 140 unset($line); 141 142 if ($type == ST_CUSTDELIVERY) { 143 $this->order_no = key($this->trans_no); 144 $cust = get_customer($this->customer_id); 145 $this->dimension_id = $cust['dimension_id']; 146 $this->dimension2_id = $cust['dimension2_id']; 147 } 148 if ($type == ST_SALESINVOICE) { 149 $this->due_date = get_invoice_duedate($this->payment, $this->document_date); 150 } 151 152 $this->src_docs = $this->trans_no; 153 $this->trans_no = 0; 154 } 155 156 // 157 // Prepares transaction for reedition updating with parent transaction data 158 // 159 function set_parent_constraints($sodata, $src_no) { 160 161 $srcdetails = get_sales_parent_lines($this->trans_type, $src_no); 162 $src_type = get_parent_type($this->trans_type); 163 164 // calculate & save: qtys on other docs and free qtys on src doc 165 $line_no = 0; $src_docs = array(); 166 // Loop speed optimisation below depends on fact 167 // that child line_items contains subset of parent lines in _the_same_ order ! 168 while (($line_no < count($this->line_items)) && ($srcline = db_fetch($srcdetails))) { 169 $line = &$this->line_items[$line_no]; 170 $src_docs[] = $src_type == ST_SALESORDER ? $srcline['order_no'] : $srcline['debtor_trans_no']; 171 while($srcline['id'] != $line->src_id) // Logic : This will increment the line_items array till sales_order line is matched. 172 { // 0002259: Fixes Delivery note bug : Parent constraints not working if sales order line deleted after delivery 173 $line_no++; 174 $line = &$this->line_items[$line_no]; 175 } 176 if ($srcline['id'] == $line->src_id) { 177 if ($this->trans_type == ST_SALESINVOICE) 178 $line->src_no = $srcline['debtor_trans_no']; 179 $line->qty_old = $line->qty_dispatched = $line->quantity; 180 $line->quantity += $srcline['quantity'] - 181 ($src_type==ST_SALESORDER ? $srcline['qty_sent'] : $srcline['qty_done']); // add free qty on src doc 182 $line_no++; 183 } 184 } 185 186 if ($src_type == ST_SALESORDER || $src_type == 0) { 187 $this->src_docs = array( $sodata['order_no']=>$sodata['version']); 188 } else { 189 // get src_data from debtor_trans 190 $this->src_docs = get_customer_trans_version($src_type, array_unique($src_docs)); 191 } 192 } 193 //------------------------------------------------------------------------- 194 // Reading document into cart 195 // 196 function read($type, $trans_no=0, $prep_child=false) { 197 198 global $SysPrefs, $Refs; 199 200 if (!is_array($trans_no)) $trans_no = array($trans_no); 201 202 if ($trans_no[0]) { // read old transaction 203 if ($type == ST_SALESORDER || $type == ST_SALESQUOTE) { // sales order || sales quotation 204 read_sales_order($trans_no[0], $this, $type); 205 } else { // other type of sales transaction 206 read_sales_trans($type, $trans_no, $this); 207 if ($this->order_no) { // free hand credit notes have no order_no 208 $sodata = get_sales_order_header($this->order_no, ST_SALESORDER); 209 $this->cust_ref = $sodata["customer_ref"]; 210 // currently currency is hard linked to debtor account 211 // $this->customer_currency = $sodata["curr_code"]; 212 $this->delivery_to = $sodata["deliver_to"]; 213 $this->delivery_address = $sodata["delivery_address"]; 214 // child transaction reedition - update with parent info unless it is freehand 215 if (!$prep_child) // this is read for view/reedition 216 $this->set_parent_constraints($sodata, $trans_no[0]); 217 } 218 } 219 // convert document into child and prepare qtys for entry 220 if ($prep_child) 221 $this->prepare_child(); 222 223 } else { // new document 224 $this->trans_type = $type; 225 $this->trans_no = 0; 226 $this->customer_currency = get_company_currency(); 227 // set new sales document defaults here 228 if (get_global_customer() != ALL_TEXT) 229 $this->customer_id = get_global_customer(); 230 else 231 $this->customer_id = ''; 232 $this->document_date = new_doc_date(); 233 if (!is_date_in_fiscalyear($this->document_date)) 234 $this->document_date = end_fiscalyear(); 235 $this->reference = $Refs->get_next($this->trans_type); 236 if ($type != ST_SALESORDER && $type != ST_SALESQUOTE) // Added 2.1 Joe Hunt 2008-11-12 237 { 238 $dim = get_company_pref('use_dimension'); 239 if ($dim > 0) 240 { 241 if ($this->customer_id == '') 242 $this->dimension_id = 0; 243 else 244 { 245 $cust = get_customer($this->customer_id); 246 $this->dimension_id = $cust['dimension_id']; 247 } 248 if ($dim > 1) 249 { 250 if ($this->customer_id == '') 251 $this->dimension2_id = 0; 252 else 253 $this->dimension2_id = $cust['dimension2_id']; 254 } 255 } 256 } 257 if ($type == ST_SALESINVOICE) { 258 $this->due_date = 259 get_invoice_duedate($this->payment, $this->document_date); 260 } else 261 $this->due_date = 262 add_days($this->document_date, $SysPrefs->default_delivery_required_by()); 263 } 264 $this->credit = get_current_cust_credit($this->customer_id); 265 } 266 267 //------------------------------------------------------------------------- 268 // Writing new/modified sales document to database. 269 // Makes parent documents for direct delivery/invoice by recurent call. 270 // $policy - 0 or 1: writeoff/return for IV, back order/cancel for DN 271 function write($policy=0) { 272 begin_transaction(); // prevents partial database changes in case of direct delivery/invoice 273 if ($this->reference != 'auto' && $this->trans_no == 0 && !is_new_reference($this->reference, $this->trans_type)) 274 { 275 commit_transaction(); 276 return -1; 277 } 278 if (count($this->src_docs) == 0 && ($this->trans_type == ST_SALESINVOICE || $this->trans_type == ST_CUSTDELIVERY)) { 279 // this is direct document - first add parent 280 $ref = $this->reference; 281 $date = $this->document_date; 282 $due_date = $this->due_date; 283 $dimension_id = $this->dimension_id; 284 $dimension2_id = $this->dimension2_id; 285 $this->trans_type = get_parent_type($this->trans_type); 286 287 $this->reference = 'auto'; 288 $trans_no = $this->write(1); 289 290 // re-read parent document converting it to child 291 $this->read($this->trans_type, $trans_no, true); 292 $this->document_date = $date; 293 $this->reference = $ref; 294 $this->due_date = $due_date; 295 $this->dimension_id = $dimension_id; 296 $this->dimension2_id = $dimension2_id; 297 } 298 $this->reference = @html_entity_decode($this->reference, ENT_QUOTES); 299 $this->Comments = @html_entity_decode($this->Comments, ENT_QUOTES); 300 foreach($this->line_items as $lineno => $line) { 301 $this->line_items[$lineno]->stock_id = @html_entity_decode($line->stock_id, ENT_QUOTES); 302 $this->line_items[$lineno]->item_description = @html_entity_decode($line->item_description, ENT_QUOTES); 303 } 304 switch($this->trans_type) { 305 case ST_SALESINVOICE: 306 $ret = write_sales_invoice($this); break; 307 case ST_CUSTCREDIT: 308 $ret = write_credit_note($this, $policy); break; 309 case ST_CUSTDELIVERY: 310 $ret = write_sales_delivery($this, $policy); break; 311 case ST_SALESORDER: 312 case ST_SALESQUOTE: 313 if ($this->trans_no==0) // new document 314 $ret = add_sales_order($this); 315 else 316 $ret = update_sales_order($this); 317 } 318 319 commit_transaction(); 320 321 return $ret; 322 } 323 324 function set_customer($customer_id, $customer_name, $currency, $discount, $payment, $cdiscount=0) 325 { 326 $this->customer_name = $customer_name; 327 $this->customer_id = $customer_id; 328 $this->default_discount = $discount; 329 $this->customer_currency = $currency; 330 $this->payment = $payment; 331 $this->payment_terms = get_payment_terms($payment); 332 $this->cash_discount = $cdiscount; 333 334 if ($this->payment_terms['cash_sale']) { 335 $this->Location = $this->pos['pos_location']; 336 $this->location_name = $this->pos['location_name']; 337 } 338 $this->credit = get_current_cust_credit($customer_id); 339 } 340 341 function set_branch($branch_id, $tax_group_id, $tax_group_name, $phone='', $email='') 342 { 343 $this->Branch = $branch_id; 344 $this->phone = $phone; 345 $this->email = $email; 346 $this->tax_group_id = $tax_group_id; 347 $this->tax_group_array = get_tax_group_items_as_array($tax_group_id); 348 } 349 350 function set_sales_type($sales_type, $sales_name, $tax_included=0, $factor=0) 351 { 352 $this->sales_type = $sales_type; 353 $this->sales_type_name = $sales_name; 354 $this->tax_included = $tax_included; 355 $this->price_factor = $factor; 356 } 357 358 function set_location($id, $name) 359 { 360 $this->Location = $id; 361 $this->location_name = $name; 362 } 363 364 function set_delivery($shipper, $destination, $address, $freight_cost=null) 365 { 366 $this->ship_via = $shipper; 367 $this->deliver_to = $destination; 368 $this->delivery_address = $address; 369 if (isset($freight_cost)) 370 $this->freight_cost = $freight_cost; 371 } 372 373 function add_to_cart($line_no, $stock_id, $qty, $price, $disc, $qty_done=0, $standard_cost=0, $description=null, $id=0, $src_no=0, 374 $src_id=0) 375 { 376 $line = new line_details($stock_id, $qty, $price, $disc, 377 $qty_done, $standard_cost, $description, $id, $src_no, $src_id); 378 379 if ($line->valid) { 380 $this->line_items[$line_no] = $line; 381 return 1; 382 } else 383 display_error(_("You have to enter valid stock code or nonempty description")); 384 return 0; 385 } 386 387 function update_cart_item($line_no, $qty, $price, $disc, $description="") 388 { 389 if ($description != "") 390 $this->line_items[$line_no]->item_description = $description; 391 $this->line_items[$line_no]->quantity = $qty; 392 $this->line_items[$line_no]->qty_dispatched = $qty; 393 $this->line_items[$line_no]->price = $price; 394 $this->line_items[$line_no]->discount_percent = $disc; 395 } 396 397 function update_add_cart_item_qty($line_no, $qty) 398 { 399 $this->line_items[$line_no]->quantity += $qty; 400 } 401 402 function remove_from_cart($line_no) 403 { 404 array_splice($this->line_items, $line_no, 1); 405 } 406 407 function clear_items() 408 { 409 unset($this->line_items); 410 $this->line_items = array(); 411 $this->sales_type = ""; 412 $this->trans_no = 0; 413 $this->customer_id = $this->order_no = 0; 414 } 415 416 function count_items() 417 { 418 $counter=0; 419 foreach ($this->line_items as $line) { 420 if ($line->quantity!=$line->qty_done) $counter++; 421 } 422 return $counter; 423 } 424 425 function get_items_total() 426 { 427 $total = 0; 428 429 foreach ($this->line_items as $ln_itm) { 430 $price = $ln_itm->line_price(); 431 $total += round($ln_itm->quantity * $price * (1 - $ln_itm->discount_percent), 432 user_price_dec()); 433 } 434 return $total; 435 } 436 437 function get_items_total_dispatch() 438 { 439 $total = 0; 440 441 foreach ($this->line_items as $ln_itm) { 442 $price = $ln_itm->line_price(); 443 $total += round(($ln_itm->qty_dispatched * $price * (1 - $ln_itm->discount_percent)), 444 user_price_dec()); 445 } 446 return $total; 447 } 448 449 function has_items_dispatch() 450 { 451 foreach ($this->line_items as $ln_itm) { 452 if ($ln_itm->qty_dispatched > 0) 453 return true; 454 } 455 return false; 456 } 457 458 function any_already_delivered() 459 { 460 /* Checks if there have been any line item processed */ 461 462 foreach ($this->line_items as $stock_item) { 463 if ($stock_item->qty_done !=0) { 464 return 1; 465 } 466 } 467 468 return 0; 469 470 } 471 472 function some_already_delivered($line_no) 473 { 474 /* Checks if there have been deliveries of a specific line item */ 475 if (isset($this->line_items[$line_no]) && 476 $this->line_items[$line_no]->qty_done != 0) { 477 return 1; 478 } 479 return 0; 480 } 481 482 function get_taxes($shipping_cost=null) 483 { 484 $items = array(); 485 $prices = array(); 486 if($shipping_cost==null) 487 $shipping_cost = $this->freight_cost; 488 489 foreach ($this->line_items as $ln_itm) { 490 $items[] = $ln_itm->stock_id; 491 $prices[] = round((( 492 $this->trans_type==ST_SALESORDER ? $ln_itm->quantity : $ln_itm->qty_dispatched) * 493 $ln_itm->line_price()* (1 - $ln_itm->discount_percent)), user_price_dec()); 494 } 495 496 $taxes = get_tax_for_items($items, $prices, $shipping_cost, 497 $this->tax_group_id, $this->tax_included, $this->tax_group_array); 498 499 // Adjustment for swiss franken, we always have 5 rappen = 1/20 franken 500 if ($this->customer_currency == 'CHF') { 501 $val = $taxes['1']['Value']; 502 $val1 = (floatval((intval(round(($val*20),0)))/20)); 503 $taxes['1']['Value'] = $val1; 504 } 505 return $taxes; 506 } 507 508 509 function get_tax_free_shipping() 510 { 511 512 if ($this->tax_included==0) 513 return $this->freight_cost; 514 else 515 return ($this->freight_cost - $this->get_shipping_tax()); 516 } 517 518 function get_shipping_tax() 519 { 520 521 $tax_items = get_shipping_tax_as_array(); 522 $tax_rate = 0; 523 if ($tax_items != null) { 524 foreach ($tax_items as $item_tax) { 525 $index = $item_tax['tax_type_id']; 526 if (isset($this->tax_group_array[$index]['rate'])) { 527 $tax_rate += $item_tax['rate']; 528 } 529 } 530 } 531 if($this->tax_included) 532 return round($this->freight_cost*$tax_rate/($tax_rate+100), user_price_dec()); 533 else 534 return round($this->freight_cost*$tax_rate/100, user_price_dec()); 535 } 536 /* 537 Returns transaction value including all taxes 538 */ 539 function get_trans_total() { 540 541 $total = $this->get_items_total() + $this->freight_cost; 542 $dec = user_price_dec(); 543 if (!$this->tax_included ) { 544 $total += $this->get_shipping_tax(); 545 $taxes = $this->get_taxes(); 546 foreach($taxes as $tax) 547 $total += round($tax['Value'], $dec); 548 } 549 550 return $total; 551 } 552 553 /* 554 Checks cart quantities on document_date. 555 Returns array of stock_ids which stock quantities would go negative on some day. 556 */ 557 function check_qoh($date=null, $location=null) 558 { 559 $low_stock = array(); 560 // check only for customer delivery and direct sales invoice 561 if (!($this->trans_type == ST_CUSTDELIVERY || ($this->trans_type == ST_SALESINVOICE && $this->trans_no==0))) 562 return $low_stock; 563 564 // collect quantities by stock_id 565 $qtys = array(); 566 foreach ($this->line_items as $line_no => $line_item) 567 { 568 if (has_stock_holding($line_item->mb_flag)) 569 { 570 if (!$this->trans_no) // new delivery 571 $qtys[$line_item->stock_id]['qty'] = $line_item->qty_dispatched + @$qtys[$line_item->stock_id]['qty']; 572 else // DN modification: check change in quantity 573 $qtys[$line_item->stock_id]['qty'] = ($line_item->qty_dispatched-$line_item->qty_old) + @$qtys[$line_item->stock_id]['qty']; 574 $qtys[$line_item->stock_id]['line'] = $line_no; 575 } 576 } 577 578 foreach($qtys as $stock_id => $sum) 579 { 580 if (check_negative_stock($stock_id, -$sum['qty'], $location ? $location : $this->Location, $date ? $date : $this->document_date)) 581 $low_stock[] = $stock_id; 582 } 583 584 return $low_stock; 585 } 586 587} /* end of class defintion */ 588 589class line_details 590{ 591 var $id; 592 var $stock_id; 593 var $item_description; 594 var $units; 595 var $mb_flag; 596 var $tax_type; 597 var $tax_type_name; 598 var $src_no; // number of src doc for this line 599 var $src_id; 600 var $price; 601 var $discount_percent; 602 603 var $standard_cost; 604 var $descr_editable; 605 606 var $valid; // validation in constructor 607 /* 608 Line quantity properties in various cart create modes: 609 610 view: 611 $quantity - quantity on current document 612 $qty_done - quantity processed on all child documents 613 $qty_dispatched - not used 614 $qty_old - not used 615 616 edit: 617 $quantity - free parent quantity including this doc (= max allowed quantity) 618 $qty_done - qty processed on child documents (= min allowed qty) 619 $qty_dispatched - quantity currently selected to process 620 $qty_old - quantity processed on this document before reedition 621 622 new child entry (view parent followed by prepare_child() call): 623 $quantity - max allowed quantity (get from parent) 624 $qty_done - qty processed on other child documents 625 $qty_dispatched - quantity currently selected to process 626 $qty_old - 0; not used 627 */ 628 var $quantity; 629 var $qty_done; 630 var $qty_dispatched; 631 var $qty_old = 0; 632 633 634 function line_details ($stock_id, $qty, $prc, $disc_percent, 635 $qty_done, $standard_cost, $description, $id=0, $src_no=0, $src_id=0) 636 { 637 /* Constructor function to add a new LineDetail object with passed params */ 638 639 $this->id = $id; 640 $this->src_no = $src_no; 641 $this->src_id = $src_id; 642 $item_row = get_item($stock_id); 643 644 if (!$item_row) 645 return; 646 647 $this->mb_flag = $item_row["mb_flag"]; 648 $this->units = $item_row["units"]; 649 $this->descr_editable = $item_row["editable"]; 650 if ($description == null || !$this->descr_editable) 651 $this->item_description = $item_row["description"]; 652 else 653 $this->item_description = $description; 654 //$this->standard_cost = $item_row["material_cost"] + $item_row["labour_cost"] + $item_row["overhead_cost"]; 655 $this->tax_type = $item_row["tax_type_id"]; 656 $this->tax_type_name = $item_row["tax_type_name"]; 657 $this->stock_id = $stock_id; 658 $this->quantity = $qty; 659 $this->qty_dispatched = $qty; 660 $this->price = $prc; 661 $this->discount_percent = $disc_percent; 662 $this->qty_done = $qty_done; 663 $this->standard_cost = $standard_cost; 664 $this->valid = true; 665 } 666 667 // get unit price as stated on document 668 function line_price() 669 { 670 return $this->price; 671 } 672} 673 674?> 675