Overview

Namespaces

  • emberlabs
    • Barcode
  • None

Classes

  • Appconfig
  • Barcode_lib
  • Config
  • Customer
  • Customer_rewards
  • Customers
  • Detailed_receivings
  • Detailed_sales
  • Dinner_table
  • Email_lib
  • emberlabs\Barcode\BarcodeBase
  • emberlabs\Barcode\Code128
  • emberlabs\Barcode\Code39
  • emberlabs\Barcode\Ean13
  • emberlabs\Barcode\Ean8
  • Employee
  • Employees
  • Giftcard
  • Giftcards
  • Home
  • Inventory
  • Inventory_low
  • Inventory_summary
  • Item
  • Item_kit
  • Item_kit_items
  • Item_kits
  • Item_lib
  • Item_quantity
  • Item_taxes
  • Items
  • Login
  • Mailchimp_lib
  • MailchimpConnector
  • Messages
  • Module
  • MY_Lang
  • No_Access
  • Person
  • Persons
  • Receiving
  • Receiving_lib
  • Receivings
  • Report
  • Reports
  • Rewards
  • Rounding_code
  • Sale
  • Sale_lib
  • Sale_suspended
  • Sales
  • Secure_Controller
  • Sms_lib
  • Specific_customer
  • Specific_discount
  • Specific_employee
  • Stock_location
  • Summary_categories
  • Summary_customers
  • Summary_discounts
  • Summary_employees
  • Summary_items
  • Summary_payments
  • Summary_report
  • Summary_sales
  • Summary_suppliers
  • Summary_taxes
  • Supplier
  • Suppliers
  • Tax
  • Tax_lib
  • Taxes
  • Token
  • Token_customer
  • Token_invoice_count
  • Token_invoice_sequence
  • Token_lib
  • Token_quote_sequence
  • Token_suspended_invoice_count
  • Token_year_invoice_count
  • Tracking_lib

Functions

  • currency_side
  • current_language
  • current_language_code
  • dateformat_bootstrap
  • dateformat_momentjs
  • db_log_queries
  • get_customer_data_row
  • get_customer_manage_table_headers
  • get_giftcard_data_row
  • get_giftcards_manage_table_headers
  • get_item_data_row
  • get_item_kit_data_row
  • get_item_kits_manage_table_headers
  • get_items_manage_table_headers
  • get_people_manage_table_headers
  • get_person_data_row
  • get_sale_data_last_row
  • get_sale_data_row
  • get_sales_manage_payments_summary
  • get_sales_manage_table_headers
  • get_supplier_data_row
  • get_suppliers_manage_table_headers
  • get_tax_data_row
  • get_taxes_manage_table_headers
  • load_config
  • load_language_files
  • load_stats
  • parse_decimals
  • pdf_create
  • quantity_decimals
  • show_report
  • show_report_if_allowed
  • to_currency
  • to_currency_no_money
  • to_decimals
  • to_quantity_decimals
  • to_tax_decimals
  • totals_decimals
  • transform_headers
  • transform_headers_readonly
  • Overview
  • Namespace
  • Class
   1:    2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16:   17:   18:   19:   20:   21:   22:   23:   24:   25:   26:   27:   28:   29:   30:   31:   32:   33:   34:   35:   36:   37:   38:   39:   40:   41:   42:   43:   44:   45:   46:   47:   48:   49:   50:   51:   52:   53:   54:   55:   56:   57:   58:   59:   60:   61:   62:   63:   64:   65:   66:   67:   68:   69:   70:   71:   72:   73:   74:   75:   76:   77:   78:   79:   80:   81:   82:   83:   84:   85:   86:   87:   88:   89:   90:   91:   92:   93:   94:   95:   96:   97:   98:   99:  100:  101:  102:  103:  104:  105:  106:  107:  108:  109:  110:  111:  112:  113:  114:  115:  116:  117:  118:  119:  120:  121:  122:  123:  124:  125:  126:  127:  128:  129:  130:  131:  132:  133:  134:  135:  136:  137:  138:  139:  140:  141:  142:  143:  144:  145:  146:  147:  148:  149:  150:  151:  152:  153:  154:  155:  156:  157:  158:  159:  160:  161:  162:  163:  164:  165:  166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176:  177:  178:  179:  180:  181:  182:  183:  184:  185:  186:  187:  188:  189:  190:  191:  192:  193:  194:  195:  196:  197:  198:  199:  200:  201:  202:  203:  204:  205:  206:  207:  208:  209:  210:  211:  212:  213:  214:  215:  216:  217:  218:  219:  220:  221:  222:  223:  224:  225:  226:  227:  228:  229:  230:  231:  232:  233:  234:  235:  236:  237:  238:  239:  240:  241:  242:  243:  244:  245:  246:  247:  248:  249:  250:  251:  252:  253:  254:  255:  256:  257:  258:  259:  260:  261:  262:  263:  264:  265:  266:  267:  268:  269:  270:  271:  272:  273:  274:  275:  276:  277:  278:  279:  280:  281:  282:  283:  284:  285:  286:  287:  288:  289:  290:  291:  292:  293:  294:  295:  296:  297:  298:  299:  300:  301:  302:  303:  304:  305:  306:  307:  308:  309:  310:  311:  312:  313:  314:  315:  316:  317:  318:  319:  320:  321:  322:  323:  324:  325:  326:  327:  328:  329:  330:  331:  332:  333:  334:  335:  336:  337:  338:  339:  340:  341:  342:  343:  344:  345:  346:  347:  348:  349:  350:  351:  352:  353:  354:  355:  356:  357:  358:  359:  360:  361:  362:  363:  364:  365:  366:  367:  368:  369:  370:  371:  372:  373:  374:  375:  376:  377:  378:  379:  380:  381:  382:  383:  384:  385:  386:  387:  388:  389:  390:  391:  392:  393:  394:  395:  396:  397:  398:  399:  400:  401:  402:  403:  404:  405:  406:  407:  408:  409:  410:  411:  412:  413:  414:  415:  416:  417:  418:  419:  420:  421:  422:  423:  424:  425:  426:  427:  428:  429:  430:  431:  432:  433:  434:  435:  436:  437:  438:  439:  440:  441:  442:  443:  444:  445:  446:  447:  448:  449:  450:  451:  452:  453:  454:  455:  456:  457:  458:  459:  460:  461:  462:  463:  464:  465:  466:  467:  468:  469:  470:  471:  472:  473:  474:  475:  476:  477:  478:  479:  480:  481:  482:  483:  484:  485:  486:  487:  488:  489:  490:  491:  492:  493:  494:  495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505:  506:  507:  508:  509:  510:  511:  512:  513:  514:  515:  516:  517:  518:  519:  520:  521:  522:  523:  524:  525:  526:  527:  528:  529:  530:  531:  532:  533:  534:  535:  536:  537:  538:  539:  540:  541:  542:  543:  544:  545:  546:  547:  548:  549:  550:  551:  552:  553:  554:  555:  556:  557:  558:  559:  560:  561:  562:  563:  564:  565:  566:  567:  568:  569:  570:  571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582:  583:  584:  585:  586:  587:  588:  589:  590:  591:  592:  593:  594:  595:  596:  597:  598:  599:  600:  601:  602:  603:  604:  605:  606:  607:  608:  609:  610:  611:  612:  613:  614:  615:  616:  617:  618:  619:  620:  621:  622:  623:  624:  625:  626:  627:  628:  629:  630:  631:  632:  633:  634:  635:  636:  637:  638:  639:  640:  641:  642:  643:  644:  645:  646:  647:  648:  649:  650:  651:  652:  653:  654:  655:  656:  657:  658:  659:  660:  661:  662:  663:  664:  665:  666:  667:  668:  669:  670:  671:  672:  673:  674:  675:  676:  677:  678:  679:  680:  681:  682:  683:  684:  685:  686:  687:  688:  689:  690:  691:  692:  693:  694:  695:  696:  697:  698:  699:  700:  701:  702:  703:  704:  705:  706:  707:  708:  709:  710:  711:  712:  713:  714:  715:  716:  717:  718:  719:  720:  721:  722:  723:  724:  725:  726:  727:  728:  729:  730:  731:  732:  733:  734:  735:  736:  737:  738:  739:  740:  741:  742:  743:  744:  745:  746:  747:  748:  749:  750:  751:  752:  753:  754:  755:  756:  757:  758:  759:  760:  761:  762:  763:  764:  765:  766:  767:  768:  769:  770:  771:  772:  773:  774:  775:  776:  777:  778:  779:  780:  781:  782:  783:  784:  785:  786:  787:  788:  789:  790:  791:  792:  793:  794:  795:  796:  797:  798:  799:  800:  801:  802:  803:  804:  805:  806:  807:  808:  809:  810:  811:  812:  813:  814:  815:  816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827:  828:  829:  830:  831:  832:  833:  834:  835:  836:  837:  838:  839:  840:  841:  842:  843:  844:  845:  846:  847:  848:  849:  850:  851:  852:  853:  854:  855:  856:  857:  858:  859:  860:  861:  862:  863:  864:  865:  866:  867:  868:  869:  870:  871:  872:  873:  874:  875:  876:  877:  878:  879:  880:  881:  882:  883:  884:  885:  886:  887:  888:  889:  890:  891:  892:  893:  894:  895:  896:  897:  898:  899:  900:  901:  902:  903:  904:  905:  906:  907:  908:  909:  910:  911:  912:  913:  914:  915:  916:  917:  918:  919:  920:  921:  922:  923:  924:  925:  926:  927:  928:  929:  930:  931:  932:  933:  934:  935:  936:  937:  938:  939:  940:  941:  942:  943:  944:  945:  946:  947:  948:  949:  950:  951:  952:  953:  954:  955:  956:  957:  958:  959:  960:  961:  962:  963:  964:  965:  966:  967:  968:  969:  970:  971:  972:  973:  974:  975:  976:  977:  978:  979:  980:  981:  982:  983:  984:  985:  986:  987:  988:  989:  990:  991:  992:  993:  994:  995:  996:  997:  998:  999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 1064: 1065: 1066: 1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 1085: 1086: 1087: 1088: 1089: 1090: 1091: 1092: 1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125: 1126: 1127: 1128: 1129: 1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 1216: 1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

class Sale_lib
{
    private $CI;

    public function __construct()
    {
        $this->CI =& get_instance();
        $this->CI->load->library('tax_lib');
        $this->CI->load->model('enums/Rounding_code');

    }

    public function get_line_sequence_options()
    {
        return array(
            '0' => $this->CI->lang->line('sales_entry'),
            '1' => $this->CI->lang->line('sales_group_by_type'),
            '2' => $this->CI->lang->line('sales_group_by_category')
        );
    }

    public function get_register_mode_options()
    {
        return array(
            'sale' => $this->CI->lang->line('sales_receipt'),
            'sale_invoice' => $this->CI->lang->line('sales_invoice'),
            'sale_quote' => $this->CI->lang->line('sales_quote')
        );
    }

    public function get_cart()
    {
        if(!$this->CI->session->userdata('sales_cart'))
        {
            $this->set_cart(array());
        }

        return $this->CI->session->userdata('sales_cart');
    }

    public function sort_and_filter_cart($cart)
    {
        if(empty($cart))
        {
            return $cart;
        }

        $filtered_cart = array();

        foreach($cart as $k=>$v)
        {
            if($v['print_option'] == '0')
            {
                $filtered_cart[] = $v;
            }
        }

        // Entry sequence (this will render kits in the expected sequence)
        if($this->CI->config->item('line_sequence') == '0')
        {
            $sort = array();
            foreach($filtered_cart as $k=>$v)
            {
                $sort['line'][$k] = $v['line'];
            }
            array_multisort($sort['line'], SORT_ASC, $filtered_cart);
        }
        // Group by Stock Type (nonstock first - type 1, stock next - type 0)
        elseif($this->CI->config->item('line_sequence') == '1')
        {
            $sort = array();
            foreach($filtered_cart as $k=>$v)
            {
                $sort['stock_type'][$k] = $v['stock_type'];
                $sort['description'][$k] = $v['description'];
                $sort['name'][$k] = $v['name'];
            }
            array_multisort($sort['stock_type'], SORT_DESC, $sort['description'], SORT_ASC, $sort['name'], SORT_ASC. $filtered_cart);
        }
        // Group by Item Category
        elseif($this->CI->config->item('line_sequence') == '2')
        {
            $sort = array();
            foreach($filtered_cart as $k=>$v)
            {
                $sort['category'][$k] = $v['stock_type'];
                $sort['description'][$k] = $v['description'];
                $sort['name'][$k] = $v['name'];
            }
            array_multisort($sort['category'], SORT_DESC, $sort['description'], SORT_ASC, $sort['name'], SORT_ASC, $filtered_cart);
        }
        // Group by entry sequence in descending sequence (the Standard)
        else
        {
            $sort = array();
            foreach($filtered_cart as $k=>$v)
            {
                $sort['line'][$k] = $v['line'];
            }
            array_multisort($sort['line'], SORT_ASC, $filtered_cart);
        }

        return $filtered_cart;
    }

    public function set_cart($cart_data)
    {
        $this->CI->session->set_userdata('sales_cart', $cart_data);
    }

    public function empty_cart()
    {
        $this->CI->session->unset_userdata('sales_cart');
    }
    
    public function get_comment() 
    {
        // avoid returning a NULL that results in a 0 in the comment if nothing is set/available
        $comment = $this->CI->session->userdata('sales_comment');

        return empty($comment) ? '' : $comment;
    }

    public function set_comment($comment) 
    {
        $this->CI->session->set_userdata('sales_comment', $comment);
    }

    public function clear_comment()     
    {
        $this->CI->session->unset_userdata('sales_comment');
    }
    
    public function get_invoice_number()
    {
        return $this->CI->session->userdata('sales_invoice_number');
    }

    public function get_quote_number()
    {
        return $this->CI->session->userdata('sales_quote_number');
    }

    public function set_invoice_number($invoice_number, $keep_custom = FALSE)
    {
        $current_invoice_number = $this->CI->session->userdata('sales_invoice_number');
        if(!$keep_custom || empty($current_invoice_number))
        {
            $this->CI->session->set_userdata('sales_invoice_number', $invoice_number);
        }
    }

    public function set_quote_number($quote_number, $keep_custom = FALSE)
    {
        $current_quote_number = $this->CI->session->userdata('sales_quote_number');
        if(!$keep_custom || empty($current_quote_number))
        {
            $this->CI->session->set_userdata('sales_quote_number', $quote_number);
        }
    }

    public function clear_invoice_number()
    {
        $this->CI->session->unset_userdata('sales_invoice_number');
    }

    public function clear_quote_number()
    {
        $this->CI->session->unset_userdata('sales_quote_number');
    }

    public function set_suspended_id($suspended_id)
    {
        $this->CI->session->set_userdata('suspended_id', $suspended_id);
    }

    public function get_suspended_id()
    {
        return $this->CI->session->userdata('suspended_id');
    }

    public function is_invoice_mode()
    {
        return ($this->CI->session->userdata('sales_invoice_number_enabled') == 'true' ||
                $this->CI->session->userdata('sales_mode') == 'sale_invoice' ||
                ($this->CI->session->userdata('sales_invoice_number_enabled') == '1') &&
                    $this->CI->config->item('invoice_enable') == TRUE);
    }

    public function is_sale_by_receipt_mode()
    {
        return ($this->CI->session->userdata('sales_mode') == 'sale');
    }

    public function is_quote_mode()
    {
        return ($this->CI->session->userdata('sales_mode') == 'sale_quote');
    }

    public function set_invoice_number_enabled($invoice_number_enabled)
    {
        return $this->CI->session->set_userdata('sales_invoice_number_enabled', $invoice_number_enabled);
    }
    
    public function is_print_after_sale() 
    {
        return ($this->CI->session->userdata('sales_print_after_sale') == 'true' ||
                $this->CI->session->userdata('sales_print_after_sale') == '1');
    }
    
    public function set_print_after_sale($print_after_sale)
    {
        return $this->CI->session->set_userdata('sales_print_after_sale', $print_after_sale);
    }
    
    public function get_email_receipt() 
    {
        return $this->CI->session->userdata('sales_email_receipt');
    }

    public function set_email_receipt($email_receipt) 
    {
        $this->CI->session->set_userdata('sales_email_receipt', $email_receipt);
    }

    public function clear_email_receipt()   
    {
        $this->CI->session->unset_userdata('sales_email_receipt');
    }

    // Multiple Payments
    public function get_payments()
    {
        if(!$this->CI->session->userdata('sales_payments'))
        {
            $this->set_payments(array());
        }

        return $this->CI->session->userdata('sales_payments');
    }

    // Multiple Payments
    public function set_payments($payments_data)
    {
        $this->CI->session->set_userdata('sales_payments', $payments_data);
    }

    // Multiple Payments
    public function add_payment($payment_id, $payment_amount)
    {
        $payments = $this->get_payments();
        if(isset($payments[$payment_id]))
        {
            //payment_method already exists, add to payment_amount
            $payments[$payment_id]['payment_amount'] = bcadd($payments[$payment_id]['payment_amount'], $payment_amount);
        }
        else
        {
            //add to existing array
            $payment = array($payment_id => array('payment_type' => $payment_id, 'payment_amount' => $payment_amount));
            
            $payments += $payment;
        }

        $this->set_payments($payments);
    }

    // Multiple Payments
    public function edit_payment($payment_id, $payment_amount)
    {
        $payments = $this->get_payments();
        if(isset($payments[$payment_id]))
        {
            $payments[$payment_id]['payment_type'] = $payment_id;
            $payments[$payment_id]['payment_amount'] = $payment_amount;
            $this->set_payments($payments);

            return TRUE;
        }

        return FALSE;
    }

    // Multiple Payments
    public function delete_payment($payment_id)
    {
        $payments = $this->get_payments();
        unset($payments[urldecode($payment_id)]);
        $this->set_payments($payments);
    }

    // Multiple Payments
    public function empty_payments()
    {
        $this->CI->session->unset_userdata('sales_payments');
    }

    // Multiple Payments
    public function get_payments_total()
    {
        $subtotal = 0;
        $this->reset_cash_flags();
        foreach($this->get_payments() as $payments)
        {
            $subtotal = bcadd($payments['payment_amount'], $subtotal);
            if($this->CI->session->userdata('cash_rounding') && $this->CI->lang->line('sales_cash') != $payments['payment_type'])
            {
                $this->CI->session->set_userdata('cash_rounding', 0);
            }
        }

        return $subtotal;
    }

    public function get_cash_rounding()
    {

    }

    /*
     * Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'paid_in_full'
     * 'subtotal', 'discounted_subtotal', 'tax_exclusive_subtotal'
     */
    public function get_totals()
    {
        $cash_rounding = $this->CI->session->userdata('cash_rounding');

        $totals = array();

        $subtotal = 0;
        $discounted_subtotal = 0;
        $tax_exclusive_subtotal = 0;
        foreach($this->get_cart() as $item)
        {
            $subtotal = bcadd($subtotal, $this->get_item_total($item['quantity'], $item['price'], $item['discount'], FALSE));
            $discounted_subtotal = bcadd($discounted_subtotal, $this->get_item_total($item['quantity'], $item['price'], $item['discount'], TRUE));
            if($this->CI->config->config['tax_included'])
            {
                $tax_exclusive_subtotal = bcadd($tax_exclusive_subtotal, $this->get_item_total_tax_exclusive($item['item_id'], $item['quantity'], $item['price'], $item['discount'], TRUE));
            }
        }

        $totals['subtotal'] = $subtotal;
        $totals['discounted_subtotal'] = $discounted_subtotal;
        $totals['tax_exclusive_subtotal'] = $tax_exclusive_subtotal;

        $total = $discounted_subtotal;
        if ($this->CI->config->config['tax_included'])
        {
            $totals['total'] = $total;
        }
        else
        {
            foreach($this->get_taxes() as $sales_tax)
            {
                $total = bcadd($total, $sales_tax['sale_tax_amount']);
            }
            $totals['total'] = $total;
        }

        if($cash_rounding)
        {
            $cash_total = $this->check_for_cash_rounding($total);
            $totals['cash_total'] = $cash_total;
        }
        else
        {
            $cash_total = $total;
        }

        $totals['cash_total'] = $cash_total;

        $payment_total = $this->get_payments_total();
        $totals['payment_total'] = $payment_total;

        $amount_due = bcsub($total, $payment_total);
        $totals['amount_due'] = $amount_due;

        $cash_amount_due = bcsub($cash_total, $payment_total);
        $totals['cash_amount_due'] = $cash_amount_due;

        if($cash_rounding)
        {
            $current_due = $cash_amount_due;
        }
        else
        {
            $current_due = $amount_due;
        }

        if($this->get_mode() == 'return')
        {
            $totals['payments_cover_total'] = $current_due >= 0;
        }
        else
        {
            $totals['payments_cover_total'] =  $current_due <= 0;
        }

        return $totals;
    }


    // Multiple Payments
    public function get_amount_due()
    {
        // Payment totals need to be identified first so that we know whether or not there is a non-cash payment involved
        $payment_total = $this->get_payments_total();
        $sales_total = $this->get_total();
        $amount_due = bcsub($sales_total, $payment_total);
        $precision = $this->CI->config->item('currency_decimals');
        $rounded_due = bccomp(round($amount_due, $precision, PHP_ROUND_HALF_EVEN), 0, $precision);
        // take care of rounding error introduced by round tripping payment amount to the browser
        return $rounded_due == 0 ? 0 : $amount_due;
    }

    public function is_payment_covering_total()
    {

        if($this->get_mode() == 'return')
        {
            return $this->get_amount_due() >= 0;
        }
        else
        {
            return $this->get_amount_due() <= 0;
        }
    }

    public function get_customer()
    {
        if(!$this->CI->session->userdata('sales_customer'))
        {
            $this->set_customer(-1);
        }

        return $this->CI->session->userdata('sales_customer');
    }

    public function set_customer($customer_id)
    {
        $this->CI->session->set_userdata('sales_customer', $customer_id);
    }

    public function remove_customer()
    {
        $this->CI->session->unset_userdata('sales_customer');
    }
    
    public function get_employee()
    {
        if(!$this->CI->session->userdata('sales_employee'))
        {
            $this->set_employee(-1);
        }

        return $this->CI->session->userdata('sales_employee');
    }

    public function set_employee($employee_id)
    {
        $this->CI->session->set_userdata('sales_employee', $employee_id);
    }

    public function remove_employee()
    {
        $this->CI->session->unset_userdata('sales_employee');
    }

    public function get_mode()
    {
        if(!$this->CI->session->userdata('sales_mode'))
        {
            if($this->CI->config->config['invoice_enable'] == '1')
            {
                $this->set_mode($this->CI->config->config['default_register_mode']);
            }
            else{
                $this->set_mode('sale');
            }
        }

        return $this->CI->session->userdata('sales_mode');
    }

    public function set_mode($mode)
    {
        $this->CI->session->set_userdata('sales_mode', $mode);
    }

    public function clear_mode()
    {
        $this->CI->session->unset_userdata('sales_mode');
    }

    public function get_dinner_table()
    {
        if(!$this->CI->session->userdata('dinner_table'))
        {
            if($this->CI->config->item('dinner_table_enable') == TRUE)
            {
                $this->set_dinner_table(1);
            }
        }

        return $this->CI->session->userdata('dinner_table');
    }

    public function set_dinner_table($dinner_table)
    {
        $this->CI->session->set_userdata('dinner_table', $dinner_table);
    }

    public function clear_table()
    {
        $this->CI->session->unset_userdata('dinner_table');
    }

    public function get_sale_location()
    {
        if(!$this->CI->session->userdata('sales_location'))
        {
            $this->set_sale_location($this->CI->Stock_location->get_default_location_id());
        }

        return $this->CI->session->userdata('sales_location');
    }

    public function set_sale_location($location)
    {
        $this->CI->session->set_userdata('sales_location', $location);
    }

    public function set_payment_type($payment_type)
    {
        $this->CI->session->set_userdata('payment_type', $payment_type);
    }

    public function get_payment_type()
    {
        return $this->CI->session->userdata('payment_type');
    }

    public function clear_sale_location()
    {
        $this->CI->session->unset_userdata('sales_location');
    }

    public function set_giftcard_remainder($value)
    {
        $this->CI->session->set_userdata('sales_giftcard_remainder', $value);
    }

    public function get_giftcard_remainder()
    {
        return $this->CI->session->userdata('sales_giftcard_remainder');
    }

    public function clear_giftcard_remainder()
    {
        $this->CI->session->unset_userdata('sales_giftcard_remainder');
    }

    public function set_rewards_remainder($value)
    {
        $this->CI->session->set_userdata('sales_rewards_remainder', $value);
    }

    public function get_rewards_remainder()
    {
        return $this->CI->session->userdata('sales_rewards_remainder');
    }

    public function clear_rewards_remainder()
    {
        $this->CI->session->unset_userdata('sales_rewards_remainder');
    }

    public function add_item(&$item_id, $quantity = 1, $item_location, $discount = 0, $price = NULL, $description = NULL, $serialnumber = NULL, $include_deleted = FALSE, $print_option = '0', $stock_type = '0')
    {
        $item_info = $this->CI->Item->get_info_by_id_or_number($item_id);

        //make sure item exists     
        if(empty($item_info))
        {
            $item_id = -1;
            return FALSE;
        }

        $item_id = $item_info->item_id;

        // Serialization and Description

        //Get all items in the cart so far...
        $items = $this->get_cart();

        //We need to loop through all items in the cart.
        //If the item is already there, get it's key($updatekey).
        //We also need to get the next key that we are going to use in case we need to add the
        //item to the cart. Since items can be deleted, we can't use a count. we use the highest key + 1.

        $maxkey = 0;                       //Highest key so far
        $itemalreadyinsale = FALSE;        //We did not find the item yet.
        $insertkey = 0;                    //Key to use for new entry.
        $updatekey = 0;                    //Key to use to update(quantity)

        foreach($items as $item)
        {
            //We primed the loop so maxkey is 0 the first time.
            //Also, we have stored the key in the element itself so we can compare.

            if($maxkey <= $item['line'])
            {
                $maxkey = $item['line'];
            }

            if($item['item_id'] == $item_id && $item['item_location'] == $item_location)
            {
                $itemalreadyinsale = TRUE;
                $updatekey = $item['line'];
                if(!$item_info->is_serialized)
                {
                    $quantity = bcadd($quantity, $items[$updatekey]['quantity']);
                }
            }
        }

        $insertkey = $maxkey + 1;
        //array/cart records are identified by $insertkey and item_id is just another field.

        if(is_null($price))
        {
            $price = $item_info->unit_price;
        }
        elseif($price == 0)
        {
            $price = 0.00;
            $discount = 0.00;
        }

        // For print purposes this simpifies line selection
        // 0 will print, 2 will not print.   The decision about 1 is made here
        if($print_option =='1')
        {
            if($price == 0)
            {
                $print_option = '2';
            }
            else
            {
                $print_option = '0';
            }
        }

        $total = $this->get_item_total($quantity, $price, $discount);
        $discounted_total = $this->get_item_total($quantity, $price, $discount, TRUE);
        //Item already exists and is not serialized, add to quantity
        if(!$itemalreadyinsale || $item_info->is_serialized)
        {
            $item = array($insertkey => array(
                    'item_id' => $item_id,
                    'item_location' => $item_location,
                    'stock_name' => $this->CI->Stock_location->get_location_name($item_location),
                    'line' => $insertkey,
                    'name' => $item_info->name,
                    'item_number' => $item_info->item_number,
                    'description' => $description != NULL ? $description : $item_info->description,
                    'serialnumber' => $serialnumber != NULL ? $serialnumber : '',
                    'allow_alt_description' => $item_info->allow_alt_description,
                    'is_serialized' => $item_info->is_serialized,
                    'quantity' => $quantity,
                    'discount' => $discount,
                    'in_stock' => $this->CI->Item_quantity->get_item_quantity($item_id, $item_location)->quantity,
                    'price' => $price,
                    'total' => $total,
                    'discounted_total' => $discounted_total,
                    'print_option' => $print_option,
                    'stock_type' => $stock_type,
                    'tax_category_id' => $item_info->tax_category_id
                )
            );
            //add to existing array
            $items += $item;
        }
        else
        {
            $line = &$items[$updatekey];
            $line['quantity'] = $quantity;
            $line['total'] = $total;
            $line['discounted_total'] = $discounted_total;
        }

        $this->set_cart($items);

        return TRUE;
    }
    
    public function out_of_stock($item_id, $item_location)
    {
        //make sure item exists     
        if($item_id != -1)
        {
            $item_info = $this->CI->Item->get_info_by_id_or_number($item_id);

            if($item_info->stock_type == '0')
            {
                $item_quantity = $this->CI->Item_quantity->get_item_quantity($item_id, $item_location)->quantity;
                $quantity_added = $this->get_quantity_already_added($item_id, $item_location);

                if($item_quantity - $quantity_added < 0)
                {
                    return $this->CI->lang->line('sales_quantity_less_than_zero');
                }
                elseif($item_quantity - $quantity_added < $item_info->reorder_level)
                {
                    return $this->CI->lang->line('sales_quantity_less_than_reorder_level');
                }
            }
        }

        return '';
    }
    
    public function get_quantity_already_added($item_id, $item_location)
    {
        $items = $this->get_cart();
        $quanity_already_added = 0;
        foreach($items as $item)
        {
            if($item['item_id'] == $item_id && $item['item_location'] == $item_location)
            {
                $quanity_already_added+=$item['quantity'];
            }
        }
        
        return $quanity_already_added;
    }
    
    public function get_item_id($line_to_get)
    {
        $items = $this->get_cart();

        foreach($items as $line=>$item)
        {
            if($line == $line_to_get)
            {
                return $item['item_id'];
            }
        }
        
        return -1;
    }

    public function edit_item($line, $description, $serialnumber, $quantity, $discount, $price)
    {
        $items = $this->get_cart();
        if(isset($items[$line]))    
        {
            $line = &$items[$line];
            $line['description'] = $description;
            $line['serialnumber'] = $serialnumber;
            $line['quantity'] = $quantity;
            $line['discount'] = $discount;
            $line['price'] = $price;
            $line['total'] = $this->get_item_total($quantity, $price, $discount);
            $line['discounted_total'] = $this->get_item_total($quantity, $price, $discount, TRUE);
            $this->set_cart($items);
        }

        return FALSE;
    }

    public function delete_item($line)
    {
        $items = $this->get_cart();
        unset($items[$line]);
        $this->set_cart($items);
    }

    public function return_entire_sale($receipt_sale_id)
    {
        //POS #
        $pieces = explode(' ', $receipt_sale_id);
        $sale_id = $pieces[1];

        $this->empty_cart();
        $this->remove_customer();

        foreach($this->CI->Sale->get_sale_items_ordered($sale_id)->result() as $row)
        {
            $this->add_item($row->item_id, -$row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price, $row->description, $row->serialnumber, TRUE, $row->print_option, $row->print_option);
        }

        $this->set_customer($this->CI->Sale->get_customer($sale_id)->person_id);
    }
    
    public function add_item_kit($external_item_kit_id, $item_location, $discount, $price_option, $kit_print_option, &$stock_warning)
    {
        //KIT #
        $pieces = explode(' ', $external_item_kit_id);
        $item_kit_id = $pieces[1];
        $result = TRUE;

        foreach($this->CI->Item_kit_items->get_info($item_kit_id) as $item_kit_item)
        {
            if($price_option == '0') // all
            {
                $price = null;
            }
            elseif($price_option == '1') // item kit only
            {
                $price = 0;
            }
            elseif($price_option == '2') // item kit plus stock items (assuming materials)
            {
                if($item_kit_item['stock_type'] == 0) // stock item
                {
                    $price = null;
                }
                else
                {
                    $price = 0;
                }
            }

            if($kit_print_option == '0') // all
            {
                $print_option = '0'; // print always
            }
            elseif($kit_print_option == '1') // priced
            {
                $print_option = '1'; // print if price not zero
            }
            elseif($kit_print_option == '2') // kit only if price is not zero
            {
                $print_option = '2'; // Do not include in list
            }

            $result &= $this->add_item($item_kit_item['item_id'], $item_kit_item['quantity'], $item_location, $discount, $price, null, null, null, $print_option, $item_kit_item['stock_type']);

            if($stock_warning == null)
            {
                $stock_warning = $this->out_of_stock($item_kit_item['item_id'], $item_location);
            }
        }
        
        return $result;
    }

    public function copy_entire_sale($sale_id)
    {
        $this->empty_cart();
        $this->remove_customer();

        foreach($this->CI->Sale->get_sale_items_ordered($sale_id)->result() as $row)
        {
            $this->add_item($row->item_id, $row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price, $row->description, $row->serialnumber, TRUE, $row->print_option);
        }

        foreach($this->CI->Sale->get_sale_payments($sale_id)->result() as $row)
        {
            $this->add_payment($row->payment_type, $row->payment_amount);
        }

        $this->set_customer($this->CI->Sale->get_customer($sale_id)->person_id);
        $this->set_employee($this->CI->Sale->get_employee($sale_id)->person_id);
    }

    public function get_cart_reordered($sale_id)
    {
        $this->empty_cart();
        foreach($this->CI->Sale->get_sale_items_ordered($sale_id)->result() as $row)
        {
            $this->add_item($row->item_id, $row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price,
                $row->description, $row->serialnumber, TRUE, $row->print_option, $row->stock_type);
        }

        return $this->CI->session->userdata('sales_cart');
    }
    
    public function copy_entire_suspended_sale($sale_id)
    {
        $this->empty_cart();
        $this->remove_customer();

        foreach($this->CI->Sale->get_sale_items($sale_id)->result() as $row)
        {
            $this->add_item($row->item_id, $row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price,
                $row->description, $row->serialnumber, TRUE, $row->print_option, $row->stock_type);
        }

        foreach($this->CI->Sale->get_sale_payments($sale_id)->result() as $row)
        {
            $this->add_payment($row->payment_type, $row->payment_amount);
        }

        $suspended_sale_info = $this->CI->Sale->get_info($sale_id)->row();
        $this->set_customer($suspended_sale_info->person_id);
        $this->set_comment($suspended_sale_info->comment);

        $this->set_invoice_number($suspended_sale_info->invoice_number);
        $this->set_quote_number($suspended_sale_info->quote_number);
        $this->set_dinner_table($suspended_sale_info->dinner_table_id);
    }

    public function copy_entire_suspended_tables_sale($sale_id)
    {
        $this->empty_cart();
        $this->remove_customer();

        foreach($this->CI->Sale_suspended->get_sale_items($sale_id)->result() as $row)
        {
            $this->add_item($row->item_id, $row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price,
                $row->description, $row->serialnumber, TRUE, $row->print_option, $row->stock_type);
        }

        foreach($this->CI->Sale_suspended->get_sale_payments($sale_id)->result() as $row)
        {
            $this->add_payment($row->payment_type, $row->payment_amount);
        }

        $suspended_sale_info = $this->CI->Sale_suspended->get_info($sale_id)->row();
        $this->set_customer($suspended_sale_info->person_id);
        $this->set_comment($suspended_sale_info->comment);

        $this->set_invoice_number($suspended_sale_info->invoice_number);
        $this->set_quote_number($suspended_sale_info->quote_number);
        $this->set_dinner_table($suspended_sale_info->dinner_table_id);
    }

    public function clear_all()
    {
        $this->set_invoice_number_enabled(FALSE);
        $this->clear_table();
        $this->empty_cart();
        $this->clear_comment();
        $this->clear_email_receipt();
        $this->clear_invoice_number();
        $this->clear_quote_number();
        $this->clear_giftcard_remainder();
        $this->empty_payments();
        $this->remove_customer();
        $this->clear_cash_flags();
    }

    public function clear_cash_flags()
    {
        $this->CI->session->unset_userdata('cash_rounding');
        $this->CI->session->unset_userdata('cash_mode');
        $this->CI->session->unset_userdata('payment_type');
    }

    public function reset_cash_flags()
    {
        if($this->CI->lang->line('payment_options_order') != 'cashdebitcredit')
        {
            $cash_mode = 1;
        }
        else
        {
            $cash_mode = 0;
        }
        $this->CI->session->set_userdata('cash_mode', $cash_mode);

        if($this->CI->config->config['cash_decimals'] < $this->CI->config->config['currency_decimals'])
        {
            $cash_rounding = 1;
        }
        else
        {
            $cash_rounding = 0;
        }
        $this->CI->session->set_userdata('cash_rounding', $cash_rounding);
    }
    
    public function is_customer_taxable()
    {
        $customer_id = $this->get_customer();
        $customer = $this->CI->Customer->get_info($customer_id);
        
        //Do not charge sales tax if we have a customer that is not taxable
        return $customer->taxable or $customer_id == -1;
    }

    /*
     * This returns taxes for VAT taxes and for pre 3.1.0 sales taxes.
     */
    public function get_taxes()
    {
        $register_mode = $this->CI->config->config['default_register_mode'];
        $tax_decimals = $this->CI->config->config['tax_decimals'];
        $customer_id = $this->get_customer();
        $customer = $this->CI->Customer->get_info($customer_id);
        $sales_taxes = array();
        //Do not charge sales tax if we have a customer that is not taxable
        if($customer->taxable or $customer_id == -1)
        {
            foreach($this->get_cart() as $line => $item)
            {
                // Start of current VAT tax apply

                $tax_info = $this->CI->Item_taxes->get_info($item['item_id']);
                $tax_group_sequence = 0;
                foreach($tax_info as $tax)
                {
                    // This computes tax for each line item and adds it to the tax type total
                    $tax_group = (float)$tax['percent'] . '% ' . $tax['name'];
                    $tax_type = Tax_lib::TAX_TYPE_VAT;
                    $tax_basis = $this->get_item_total($item['quantity'], $item['price'], $item['discount'], TRUE);
                    $tax_amount = 0;

                    if($this->CI->config->config['tax_included'])
                    {
                        $tax_amount = $this->get_item_tax($item['quantity'], $item['price'], $item['discount'], $tax['percent']);
                    }
                    elseif($this->CI->config->config['customer_sales_tax_support'] == '0')
                    {
                        $tax_amount = $this->CI->tax_lib->get_sales_tax_for_amount($tax_basis, $tax['percent'], '0', $tax_decimals);
                    }

                    if($tax_amount <> 0)
                    {
                        $this->CI->tax_lib->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $tax['percent'], $tax_basis, $tax_amount, $tax_group_sequence, '0', -1);
                        $tax_group_sequence += 1;
                    }
                }

                $tax_category = '';
                $tax_rate = '';
                $rounding_code = Rounding_code::HALF_UP;
                $tax_group_sequence = 0;
                $tax_code = '';

                if($this->CI->config->config['customer_sales_tax_support'] == '1')
                {
                    // Now calculate what the sales taxes should be (storing them in the $sales_taxes array
                    $this->CI->tax_lib->apply_sales_tax($item, $customer->city, $customer->state, $customer->sales_tax_code, $register_mode, 0, $sales_taxes, $tax_category, $tax_rate, $rounding_code, $tax_group_sequence, $tax_code);
                }

            }

            $this->CI->tax_lib->round_sales_taxes($sales_taxes);

        }

        return $sales_taxes;
    }

    public function apply_customer_discount($discount_percent)
    {   
        // Get all items in the cart so far...
        $items = $this->get_cart();
        
        foreach($items as &$item)
        {
            $quantity = $item['quantity'];
            $price = $item['price'];

            // set a new discount only if the current one is 0
            if($item['discount'] == 0)
            {
                $item['discount'] = $discount_percent;
                $item['total'] = $this->get_item_total($quantity, $price, $discount_percent);
                $item['discounted_total'] = $this->get_item_total($quantity, $price, $discount_percent, TRUE);
            }
        }

        $this->set_cart($items);
    }
    
    public function get_discount()
    {
        $discount = 0;
        foreach($this->get_cart() as $item)
        {
            if($item['discount'] > 0)
            {
                $item_discount = $this->get_item_discount($item['quantity'], $item['price'], $item['discount']);
                $discount = bcadd($discount, $item_discount);
            }
        }

        return $discount;
    }

    public function get_subtotal($include_discount = FALSE, $exclude_tax = FALSE)
    {
        return $this->calculate_subtotal($include_discount, $exclude_tax);
    }
    
    public function get_item_total_tax_exclusive($item_id, $quantity, $price, $discount_percentage, $include_discount = FALSE) 
    {
        $tax_info = $this->CI->Item_taxes->get_info($item_id);
        $item_price = $this->get_item_total($quantity, $price, $discount_percentage, $include_discount);
        // only additive tax here
        foreach($tax_info as $tax)
        {
            $tax_percentage = $tax['percent'];
            $item_price = bcsub($item_price, $this->get_item_tax($quantity, $price, $discount_percentage, $tax_percentage));
        }
        
        return $item_price;
    }
    
    public function get_item_total($quantity, $price, $discount_percentage, $include_discount = FALSE)  
    {
        $total = bcmul($quantity, $price);
        if($include_discount)
        {
            $discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);

            return bcsub($total, $discount_amount);
        }

        return $total;
    }
    
    public function get_item_discount($quantity, $price, $discount_percentage)
    {
        $total = bcmul($quantity, $price);
        $discount_fraction = bcdiv($discount_percentage, 100);

        return bcmul($total, $discount_fraction);
    }
    
    public function get_item_tax($quantity, $price, $discount_percentage, $tax_percentage) 
    {
        $price = $this->get_item_total($quantity, $price, $discount_percentage, TRUE);
        if($this->CI->config->config['tax_included'])
        {
            $tax_fraction = bcadd(100, $tax_percentage);
            $tax_fraction = bcdiv($tax_fraction, 100);
            $price_tax_excl = bcdiv($price, $tax_fraction);

            return bcsub($price, $price_tax_excl);
        }
        $tax_fraction = bcdiv($tax_percentage, 100);

        return bcmul($price, $tax_fraction);
    }

    public function calculate_subtotal($include_discount = FALSE, $exclude_tax = FALSE)
    {
        $subtotal = 0;
        foreach($this->get_cart() as $item)
        {
            if($exclude_tax && $this->CI->config->config['tax_included'])
            {
                $subtotal = bcadd($subtotal, $this->get_item_total_tax_exclusive($item['item_id'], $item['quantity'], $item['price'], $item['discount'], $include_discount));
            }
            else 
            {
                $subtotal = bcadd($subtotal, $this->get_item_total($item['quantity'], $item['price'], $item['discount'], $include_discount));
            }
        }

        return $subtotal;
    }

    public function get_total()
    {
        $total = $this->calculate_subtotal(TRUE);

        $cash_rounding = $this->CI->session->userdata('cash_rounding');

        foreach($this->get_taxes() as $sales_tax)
        {
            $total = bcadd($total, $sales_tax['sale_tax_amount']);
        }

        if($cash_rounding)
        {
            $rounded_total = $this->check_for_cash_rounding($total);
            return $rounded_total;
        }
        return $total;
    }

    public function get_empty_tables()
    {
        return $this->CI->Dinner_table->get_empty_tables();     
    }

    public function check_for_cash_rounding($total)
    {
        $cash_decimals = $this->CI->config->config['cash_decimals'];
        $cash_rounding_code = $this->CI->config->config['cash_rounding_code'];
        $rounded_total = $total;

        if($cash_rounding_code == Rounding_code::HALF_UP)
        {
            $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_UP);
        }
        elseif($cash_rounding_code == Rounding_code::HALF_DOWN)
        {
            $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_DOWN);
        }
        elseif($cash_rounding_code == Rounding_code::HALF_EVEN)
        {
            $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_EVEN);
        }
        elseif($cash_rounding_code == Rounding_code::HALF_ODD)
        {
            $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_UP);
        }
        elseif($cash_rounding_code == Rounding_code::ROUND_UP)
        {
            $fig = (int) str_pad('1', $cash_decimals, '0');
            $rounded_total = (ceil($total * $fig) / $fig);
        }
        elseif($cash_rounding_code == Rounding_code::ROUND_DOWN)
        {
            $fig = (int) str_pad('1', $cash_decimals, '0');
            $rounded_total = (floor($total * $fig) / $fig);
        }
        elseif($cash_rounding_code == Rounding_code::HALF_FIVE)
        {
            $rounded_total = round($total / 5) * 5;
        }

        return $rounded_total;
    }
}

?>
API documentation generated by ApiGen